12 | Projection Example
13 | Collision Example
14 | Interaction Example
15 |
16 |
17 |
--------------------------------------------------------------------------------
/docs/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": "eslint:recommended",
8 | "parserOptions": {
9 | "sourceType": "module"
10 | },
11 | "rules": {
12 | "indent": [
13 | "error",
14 | 2
15 | ],
16 | "linebreak-style": [
17 | "error",
18 | "unix"
19 | ],
20 | "quotes": [
21 | "error",
22 | "single"
23 | ],
24 | "semi": [
25 | "error",
26 | "always"
27 | ]
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled source #
2 | ###################
3 | *.com
4 | *.class
5 | *.dll
6 | *.exe
7 | *.o
8 | *.so
9 |
10 | # Packages #
11 | ############
12 | # it's better to unpack these files and commit the raw source
13 | # git has its own built in compression methods
14 | *.7z
15 | *.dmg
16 | *.gz
17 | *.iso
18 | *.jar
19 | *.rar
20 | *.tar
21 | *.zip
22 |
23 | # Logs and databases #
24 | ######################
25 | *.log
26 | *.sql
27 | *.sqlite
28 |
29 | # OS generated files #
30 | ######################
31 | .DS_Store
32 | .DS_Store?
33 | ._*
34 | .Spotlight-V100
35 | .Trashes
36 | ehthumbs.db
37 | Thumbs.db
38 |
39 | node_modules/
40 | docs/node_modules/
41 |
42 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "phaser3-plugin-isometric-docs",
3 | "version": "1.0.0",
4 | "description": "The docs page for phaser3-plugin-isometric",
5 | "main": "index.js",
6 | "repository": "https://sebashwa.github.io/phaser3-plugin-isometric",
7 | "author": "sebashwa",
8 | "license": "MIT",
9 | "devDependencies": {
10 | "babel-core": "^6.26.0",
11 | "babel-loader": "^7.1.4",
12 | "babel-preset-env": "^1.6.1",
13 | "eslint": "^4.19.1",
14 | "raw-loader": "^0.5.1",
15 | "webpack": "^4.5.0",
16 | "webpack-cli": "^2.0.14",
17 | "webpack-dev-server": "^3.1.1"
18 | },
19 | "dependencies": {
20 | "phaser": "^3.x",
21 | "phaser3-plugin-isometric": "^0.0.7"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "browser": true,
4 | "node": true,
5 | "commonjs": true,
6 | "es6": true
7 | },
8 | "extends": "eslint:recommended",
9 | "parserOptions": {
10 | "sourceType": "module"
11 | },
12 | "globals": {
13 | "Phaser": false
14 | },
15 | "rules": {
16 | "indent": [
17 | "error",
18 | 2
19 | ],
20 | "linebreak-style": [
21 | "error",
22 | "unix"
23 | ],
24 | "quotes": [
25 | "error",
26 | "single"
27 | ],
28 | "semi": [
29 | "error",
30 | "always"
31 | ]
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "phaser3-plugin-isometric",
3 | "description": "Feature-packed axonometric plugin for Phaser 3 which stays true to the Phaser API.",
4 | "contributors": {
5 | "email": "sebashwa@mailbox.org"
6 | },
7 | "keywords": [
8 | "phaser",
9 | "phaser3",
10 | "phaser 3",
11 | "isometric",
12 | "plugin",
13 | "axonometric"
14 | ],
15 | "license": "MIT",
16 | "version": "0.0.7",
17 | "devDependencies": {
18 | "babel-core": "^6.26.0",
19 | "babel-loader": "^7.1.4",
20 | "babel-preset-env": "^1.6.1",
21 | "eslint": "^4.19.1",
22 | "webpack": "^4.4.1",
23 | "webpack-cli": "^3.1.2"
24 | },
25 | "peerDependencies": {
26 | "phaser": "3.x"
27 | },
28 | "repository": {
29 | "type": "git",
30 | "url": "https://github.com/sebashwa/phaser-plugin-isometric.git"
31 | },
32 | "main": "dist/phaser-plugin-isometric.js"
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Lewis Lane
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/docs/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 |
3 | module.exports = {
4 | mode: 'development',
5 | entry: {
6 | projection: './src/IsoProjectionExample.js',
7 | collision: './src/IsoCollisionExample.js',
8 | interaction: './src/IsoInteractionExample.js'
9 | },
10 | module: {
11 | rules: [
12 | { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' },
13 | { test: [/\.vert$/, /\.frag$/], use: 'raw-loader' }
14 | ]
15 | },
16 | output: {
17 | path: __dirname + '/dist',
18 | publicPath: '/phaser3-plugin-isometric/dist',
19 | filename: '[name].bundle.js'
20 | },
21 | plugins: [
22 | new webpack.DefinePlugin({
23 | WEBGL_RENDERER: true,
24 | CANVAS_RENDERER: true
25 | })
26 | ],
27 | devServer: {
28 | contentBase: __dirname,
29 | publicPath: '/dist'
30 | },
31 | optimization: {
32 | splitChunks: {
33 | cacheGroups: {
34 | commons: {
35 | name: 'commons',
36 | chunks: 'initial',
37 | minChunks: 2,
38 | minSize: 0
39 | }
40 | }
41 | },
42 | occurrenceOrder: true
43 | }
44 | };
45 |
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a **WIP** fork of [lewster32/phaser-plugin-isometric](https://github.com/lewster32/phaser-plugin-isometric) to make it work with Phaser 3.
2 |
3 | ## Description
4 | Phaser Isometric is a comprehensive axonometric plugin for Phaser which provides an API for handling axonometric projection of assets in 3D space to the screen.
5 | The goal has been to mimic as closely as possible the existing APIs provided by Phaser for standard orthogonal 2D projection, but add a third dimension.
6 | Also included is an Arcade-based 3D AABB physics engine, which again is closely equivalent in functionality and its API.
7 |
8 | ## Features
9 |
10 | * Familiar Phaser API - if you've grasped the basics of Phaser, you can use this!
11 | * 3D geometry helpers in the form of Point3 and Cube
12 | * Adjustable axonometric projection angle to allow for classic 2:1 pixel dimetric, true 120° isometric or any angle you like via ```scene.isometric.projectionAngle```
13 | * Simple x+y (with z fudging)
14 | * Arcade Physics derived 3D physics engine (**Working, but needs refactoring!**)
15 | * Helpful debug utilities (**Not working yet!**)
16 | * Familiar factory methods added to GameObjectFactory and GameObjectCreator so you can do ```scene.add.isoSprite```
17 |
18 | ## Examples
19 | Check out the [docs](https://github.com/sebashwa/phaser3-plugin-isometric/tree/master/docs) folder and the [github-page](https://sebashwa.github.io/phaser3-plugin-isometric/)
20 |
--------------------------------------------------------------------------------
/docs/src/IsoInteractionExample.js:
--------------------------------------------------------------------------------
1 | import Phaser, { Game, Scene } from 'phaser';
2 | import IsoPlugin from 'phaser3-plugin-isometric';
3 |
4 | class IsoInteractionExample extends Scene {
5 | constructor() {
6 | const sceneConfig = {
7 | key: 'IsoInteractionExample',
8 | mapAdd: { isoPlugin: 'iso' }
9 | };
10 |
11 | super(sceneConfig);
12 | }
13 |
14 | preload() {
15 | this.load.image('tile', '../dist/assets/tile.png');
16 | this.load.scenePlugin({
17 | key: 'IsoPlugin',
18 | url: IsoPlugin,
19 | sceneKey: 'iso'
20 | });
21 | }
22 |
23 | create() {
24 | this.isoGroup = this.add.group();
25 |
26 | this.iso.projector.origin.setTo(0.5, 0.3);
27 |
28 | // Add some tiles to our scene
29 | this.spawnTiles();
30 | }
31 |
32 | spawnTiles() {
33 | var tile;
34 |
35 | for (var xx = 0; xx < 256; xx += 38) {
36 | for (var yy = 0; yy < 256; yy += 38) {
37 | tile = this.add.isoSprite(xx, yy, 0, 'tile', this.isoGroup);
38 | tile.setInteractive();
39 |
40 | tile.on('pointerover', function() {
41 | this.setTint(0x86bfda);
42 | this.isoZ += 5;
43 | });
44 |
45 | tile.on('pointerout', function() {
46 | this.clearTint();
47 | this.isoZ -= 5;
48 | });
49 | }
50 | }
51 | }
52 | }
53 |
54 | let config = {
55 | type: Phaser.AUTO,
56 | width: 800,
57 | height: 600,
58 | pixelArt: true,
59 | scene: IsoInteractionExample
60 | };
61 |
62 | new Game(config);
63 |
--------------------------------------------------------------------------------
/docs/src/IsoProjectionExample.js:
--------------------------------------------------------------------------------
1 | import Phaser, { Scene, Game } from 'phaser';
2 | import IsoPlugin from 'phaser3-plugin-isometric';
3 |
4 | class IsoProjectionExample extends Scene {
5 | constructor() {
6 | super({
7 | key: 'IsoProjectionExample',
8 | mapAdd: { isoPlugin: 'iso' }
9 | });
10 | }
11 |
12 | preload() {
13 | this.load.image('cube', '../dist/assets/cube.png');
14 | this.load.scenePlugin({
15 | key: 'IsoPlugin',
16 | url: IsoPlugin,
17 | sceneKey: 'iso'
18 | });
19 | }
20 |
21 | create() {
22 | // Set the origin of the isometric projection to the mid top of the screen
23 | this.iso.projector.origin.setTo(0.5, 0.1);
24 |
25 | // Even though the children are added back to front, it is sorted the right way
26 | // because depth value is set on the IsoSprites and Phaser 3 sorts after that by default.
27 | for (var xx = 256; xx > 0; xx -= 48) {
28 | for (var yy = 256; yy > 0; yy -= 48) {
29 | // Create a cube using the new isoSprite factory method at the specified position.
30 | const isoCube = this.add.isoSprite(xx, yy, 0, 'cube');
31 |
32 | // Add a tween so we can see the depth sorting works on updates
33 | this.tweens.add({
34 | targets: isoCube,
35 | isoX: 256 - xx + 32,
36 | duration: 2000,
37 | ease: 'Quad.easeInOut',
38 | delay: 0,
39 | yoyo: true,
40 | repeat: Infinity
41 | });
42 | }
43 | }
44 | }
45 |
46 | update() {
47 | // We can interact with the cubes
48 | // Pick a pseudo-random cube and let it sink on click / touch
49 | if (this.input.activePointer.justDown) {
50 | const cube = this.children.list[Math.trunc(Math.random() * 35)];
51 | cube.isoZ -= 48;
52 | }
53 | }
54 | }
55 |
56 | let config = {
57 | type: Phaser.AUTO,
58 | width: 600,
59 | height: 600,
60 | pixelArt: true,
61 | scene: IsoProjectionExample
62 | };
63 |
64 | new Game(config);
65 |
--------------------------------------------------------------------------------
/docs/src/IsoCollisionExample.js:
--------------------------------------------------------------------------------
1 | import Phaser, { Game, Scene } from 'phaser';
2 | import IsoPlugin, { IsoPhysics } from 'phaser3-plugin-isometric';
3 |
4 | class IsoCollisionExample extends Scene {
5 | constructor() {
6 | const sceneConfig = {
7 | key: 'IsoCollisionExample',
8 | mapAdd: { isoPlugin: 'iso', isoPhysics: 'isoPhysics' }
9 | };
10 |
11 | super(sceneConfig);
12 | }
13 |
14 | preload() {
15 | this.load.image('cube', '../dist/assets/cube.png');
16 | this.load.scenePlugin({
17 | key: 'IsoPlugin',
18 | url: IsoPlugin,
19 | sceneKey: 'iso'
20 | });
21 |
22 | this.load.scenePlugin({
23 | key: 'IsoPhysics',
24 | url: IsoPhysics,
25 | sceneKey: 'isoPhysics'
26 | });
27 | }
28 |
29 | create() {
30 | this.isoGroup = this.add.group();
31 |
32 | // Apply some gravity on our cubes
33 | this.isoPhysics.world.gravity.setTo(0, 0, -500);
34 |
35 | this.isoPhysics.projector.origin.setTo(0.5, 0.3);
36 |
37 | // Add some first cubes to our scene
38 | this.spawnCubes();
39 | }
40 |
41 | update() {
42 | // Collide cubes against each other
43 | this.isoPhysics.world.collide(this.isoGroup);
44 |
45 | // Moooore cuuuubes
46 | if (this.input.activePointer.justDown) {
47 | this.spawnCubes();
48 | }
49 | }
50 |
51 | spawnCubes() {
52 | let cube;
53 | for (let xx = 256; xx > 0; xx -= 64) {
54 | for (let yy = 256; yy > 0; yy -= 64) {
55 | // Add a cube which is way above the ground
56 | cube = this.add.isoSprite(xx, yy, 600, 'cube', this.isoGroup);
57 |
58 | // Enable the physics body on this cube
59 | this.isoPhysics.world.enable(cube);
60 |
61 | // Collide with the world bounds so it doesn't go falling forever or fly off the screen!
62 | cube.body.collideWorldBounds = true;
63 |
64 | // Add a full bounce on the x and y axes, and a bit on the z axis.
65 | cube.body.bounce.set(1, 1, 0.2);
66 |
67 | // Send the cubes off in random x and y directions! Wheee!
68 | const randomX = Math.trunc((Math.random() * 100 - 50));
69 | const randomY = Math.trunc((Math.random() * 100 - 50));
70 | cube.body.velocity.setTo(randomX, randomY, 0);
71 | }
72 | }
73 | }
74 | }
75 |
76 | let config = {
77 | type: Phaser.AUTO,
78 | width: 800,
79 | height: 600,
80 | pixelArt: true,
81 | scene: IsoCollisionExample
82 | };
83 |
84 | new Game(config);
85 |
--------------------------------------------------------------------------------
/docs/dist/projection.bundle.js:
--------------------------------------------------------------------------------
1 | !function(e){function t(t){for(var r,u,a=t[0],c=t[1],l=t[2],f=0,p=[];f0;e-=48)for(var t=256;t>0;t-=48){var n=this.add.isoSprite(e,t,0,"cube");this.tweens.add({targets:n,isoX:256-e+32,duration:2e3,ease:"Quad.easeInOut",delay:0,yoyo:!0,repeat:1/0})}}},{key:"update",value:function(){this.input.activePointer.justDown&&(this.children.list[Math.trunc(35*Math.random())].isoZ-=48)}}]),t}(),l={type:i.default.AUTO,width:600,height:600,pixelArt:!0,scene:c};new o.Game(l)}});
--------------------------------------------------------------------------------
/docs/dist/interaction.bundle.js:
--------------------------------------------------------------------------------
1 | !function(e){function t(t){for(var r,u,a=t[0],s=t[1],l=t[2],f=0,p=[];f0;t-=64)for(var o=256;o>0;o-=64){e=this.add.isoSprite(t,o,600,"cube",this.isoGroup),this.isoPhysics.world.enable(e),e.body.collideWorldBounds=!0,e.body.bounce.set(1,1,.2);var n=Math.trunc(100*Math.random()-50),r=Math.trunc(100*Math.random()-50);e.body.velocity.setTo(n,r,0)}}}]),t}(),l={type:i.default.AUTO,width:800,height:600,pixelArt:!0,scene:c};new r.Game(l)}});
--------------------------------------------------------------------------------
/src/Projector.js:
--------------------------------------------------------------------------------
1 | import Point3 from './Point3';
2 | const Point = Phaser.Geom.Point;
3 |
4 | // Projection angles
5 | export const CLASSIC = Math.atan(0.5);
6 | export const ISOMETRIC = Math.PI / 6;
7 | export const MILITARY = Math.PI / 4;
8 |
9 | /**
10 | * @class Projector
11 | *
12 | * @classdesc
13 | * Creates a new Isometric Projector object, which has helpers for projecting x, y and z coordinates into axonometric x and y equivalents.
14 | */
15 | class Projector {
16 | /**
17 | * @constructor
18 | * @param {Phaser.Game} game - The current game object.
19 | * @param {number} projectionAngle - The angle of the axonometric projection in radians. Defaults to approx. 0.4636476 (Math.atan(0.5) which is suitable for 2:1 pixel art dimetric)
20 | * @return {Cube} This Cube object.
21 | */
22 | constructor(scene, projectionAngle) {
23 | /**
24 | * @property {Phaser.Scene} scne - The current scene object.
25 | */
26 | this.scene = scene;
27 |
28 | /**
29 | * @property {array} _transform - The pre-calculated axonometric transformation values.
30 | * @private
31 | */
32 | this._transform = null;
33 |
34 | /**
35 | * @property {number} _projectionAngle - The cached angle of projection in radians.
36 | * @private
37 | */
38 | this._projectionAngle = 0;
39 |
40 | /**
41 | * @property {number} projectionAngle - The angle of projection in radians.
42 | * @default
43 | */
44 | this.projectionAngle = projectionAngle || CLASSIC;
45 |
46 | /**
47 | * @property {Phaser.Geom.Point} origin - The x and y offset multipliers as a ratio of the game world size.
48 | * @default
49 | */
50 | this.origin = new Point(0.5, 0.5);
51 | }
52 |
53 | /**
54 | * @name Projector#projectionAngle
55 | * @property {number} projectionAngle - The angle of axonometric projection.
56 | */
57 | set projectionAngle(angle) {
58 | if (angle === this._projectionAngle) { return; }
59 |
60 | this._projectionAngle = angle;
61 | this._transform = [Math.cos(this._projectionAngle), Math.sin(this._projectionAngle)];
62 | }
63 |
64 | get projectionAngle() {
65 | return this._projectionAngle;
66 | }
67 |
68 | /**
69 | * Use axonometric projection to transform a 3D Point3 coordinate to a 2D Point coordinate. If given the coordinates will be set into the object, otherwise a brand new Point object will be created and returned.
70 | * @method Projector#project
71 | * @param {Point3} point3 - The Point3 to project from.
72 | * @param {Phaser.Geom.Point} out - The Point to project to.
73 | * @return {Phaser.Geom.Point} The transformed Point.
74 | */
75 | project(point3, out = new Point()) {
76 | out.x = (point3.x - point3.y) * this._transform[0];
77 | out.y = ((point3.x + point3.y) * this._transform[1]) - point3.z;
78 |
79 | const { width, height } = this.scene.sys.game.config;
80 | out.x += width * this.origin.x;
81 | out.y += height * this.origin.y;
82 |
83 | return out;
84 | }
85 |
86 | /**
87 | * Use axonometric projection to transform a 3D Point3 coordinate to a 2D Point coordinate, ignoring the z-axis. If given the coordinates will be set into the object, otherwise a brand new Point object will be created and returned.
88 | * @method Projector#projectXY
89 | * @param {Point3} point3 - The Point3 to project from.
90 | * @param {Phaser.Geom.Point} out - The Point to project to.
91 | * @return {Phaser.Geom.Point} The transformed Point.
92 | */
93 | projectXY(point3, out = new Point()) {
94 | out.x = (point3.x - point3.y) * this._transform[0];
95 | out.y = (point3.x + point3.y) * this._transform[1];
96 |
97 | out.x += this.game.world.width * this.origin.x;
98 | out.y += this.game.world.height * this.origin.y;
99 |
100 | return out;
101 | }
102 |
103 | /**
104 | * Use reverse axonometric projection to transform a 2D Point coordinate to a 3D Point3 coordinate. If given the coordinates will be set into the object, otherwise a brand new Point3 object will be created and returned.
105 | * @method Projector#unproject
106 | * @param {Phaser.Geom.Point} point - The Point to project from.
107 | * @param {Point3} out - The Point3 to project to.
108 | * @param {number} [z] - Specified z-plane to project to.
109 | * @return {Point3} The transformed Point3.
110 | */
111 | unproject(point, out = new Point3(), z = 0) {
112 | const x = point.x - this.game.world.x - (this.game.world.width * this.origin.x);
113 | const y = point.y - this.game.world.y - (this.game.world.height * this.origin.y) + z;
114 |
115 | out.x = x / (2 * this._transform[0]) + y / (2 * this._transform[1]);
116 | out.y = -(x / (2 * this._transform[0])) + y / (2 * this._transform[1]);
117 | out.z = z;
118 |
119 | return out;
120 | }
121 | }
122 |
123 | export default Projector;
124 |
--------------------------------------------------------------------------------
/src/IsoPlugin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 |
4 | * Copyright (c) 2015 Lewis Lane
5 |
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | /**
26 | * @author Lewis Lane
27 | * @copyright 2015 Lewis Lane (Rotates.org)
28 | * @license {@link http://opensource.org/licenses/MIT|MIT License}
29 | */
30 |
31 | import Projector from './Projector';
32 | import IsoSprite from './IsoSprite';
33 | export { default as IsoPhysics } from './physics/IsoPhysics';
34 |
35 | // Type consts
36 |
37 | /**
38 | * @class IsometricPlugin
39 | *
40 | * @classdesc
41 | * Isometric is a comprehensive axonometric plugin for Phaser which provides an API for handling axonometric projection of assets in 3D space to the screen.
42 | * The goal has been to mimic as closely as possible the existing APIs provided by Phaser for standard orthogonal 2D projection, but add a third dimension.
43 | * Also included is an Arcade-based 3D AABB physics engine, which again is closely equivalent in functionality and its API.
44 | */
45 | export default class IsoPlugin {
46 | /**
47 | * @constructor
48 | * @param {Phaser.Scene} scene The current scene instance
49 | */
50 | constructor(scene) {
51 | this.scene = scene;
52 | this.systems = scene.sys;
53 |
54 | if (!scene.sys.settings.isBooted) {
55 | scene.sys.events.once('boot', this.boot, this);
56 | }
57 |
58 | this.projector = new Projector(scene, scene.isometricType);
59 |
60 | /**
61 | * Create a new IsoSprite with specific position and sprite sheet key.
62 | *
63 | * @method Phaser.GameObjectFactory#isoSprite
64 | * @param {number} x - X position of the new IsoSprite.
65 | * @param {number} y - Y position of the new IsoSprite.
66 | * @param {number} y - Z position of the new IsoSprite.
67 | * @param {string|Phaser.RenderTexture|PIXI.Texture} key - This is the image or texture used by the Sprite during rendering. It can be a string which is a reference to the Cache entry, or an instance of a RenderTexture or PIXI.Texture.
68 | * @param {string|number} [frame] - If the sprite uses an image from a texture atlas or sprite sheet you can pass the frame here. Either a number for a frame ID or a string for a frame name.
69 | * @returns {IsoSprite} the newly created IsoSprite object.
70 | */
71 |
72 | Phaser.GameObjects.GameObjectCreator.register('isoSprite', function (x, y, z, key, frame) {
73 | return new IsoSprite(this.scene, x, y, z, key, frame);
74 | });
75 |
76 | /**
77 | * Create a new IsoSprite with specific position and sprite sheet key.
78 | *
79 | * @method Phaser.GameObjectFactory#isoSprite
80 | * @param {number} x - X position of the new IsoSprite.
81 | * @param {number} y - Y position of the new IsoSprite.
82 | * @param {number} y - Z position of the new IsoSprite.
83 | * @param {string|Phaser.RenderTexture|PIXI.Texture} key - This is the image or texture used by the Sprite during rendering. It can be a string which is a reference to the Cache entry, or an instance of a RenderTexture or PIXI.Texture.
84 | * @param {string|number} [frame] - If the sprite uses an image from a texture atlas or sprite sheet you can pass the frame here. Either a number for a frame ID or a string for a frame name.
85 | * @param {Phaser.Group} [group] - Optional Group to add the object to. If not specified it will be added to the World group.
86 | * @returns {IsoSprite} the newly created IsoSprite object.
87 | */
88 | Phaser.GameObjects.GameObjectFactory.register('isoSprite', function (x, y, z, key, group, frame = 0) {
89 | const sprite = new IsoSprite(this.scene, x, y, z, key, frame);
90 |
91 | if (typeof group === 'undefined') {
92 | this.displayList.add(sprite);
93 | this.updateList.add(sprite);
94 | } else {
95 | group.add(sprite, true);
96 | }
97 |
98 | return sprite;
99 | });
100 | }
101 |
102 | boot() {
103 | }
104 |
105 | static register(PluginManager) {
106 | PluginManager.register('IsoPlugin', IsoPlugin, 'isoPlugin');
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/Point3.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class Point3
3 | *
4 | * @classdesc
5 | * The Point3 object represents a location in a three-dimensional coordinate system,
6 | * where x and y represent the horizontal axes and z represents the vertical axis.
7 | * The following code creates a point at (0,0,0):
8 | * `var myPoint = new Point3();`
9 | *
10 | * Creates a new Point3 object. If you pass no parameters a Point3 is created set to (0, 0, 0).
11 | */
12 | class Point3 {
13 | /**
14 | * @constructor
15 | * @param {number} [x=0] - The horizontal X position of this Point.
16 | * @param {number} [y=0] - The horizontal Y position of this Point.
17 | * @param {number} [z=0] - The vertical position of this Point.
18 | */
19 | constructor(x = 0, y = 0, z = 0) {
20 | /**
21 | * @property {number} x - The x value of the point.
22 | */
23 | this.x = x;
24 |
25 | /**
26 | * @property {number} y - The y value of the point.
27 | */
28 | this.y = y;
29 |
30 | /**
31 | * @property {number} z - The z value of the point.
32 | */
33 | this.z = z;
34 | }
35 |
36 | /**
37 | * Copies the x, y and z properties from any given object to this Point3.
38 | *
39 | * @method Point3#copyFrom
40 | * @param {any} source - The object to copy from.
41 | * @return {Point3} This Point3 object.
42 | */
43 | copyFrom(source) {
44 | return this.setTo(source.x, source.y, source.z);
45 | }
46 |
47 | /**
48 | * Copies the x, y and z properties from this Point3 to any given object.
49 | *
50 | * @method Point3#copyTo
51 | * @param {any} dest - The object to copy to.
52 | * @return {Object} The dest object.
53 | */
54 | copyTo(dest) {
55 | dest.x = this.x;
56 | dest.y = this.y;
57 | dest.z = this.z;
58 |
59 | return dest;
60 | }
61 |
62 | /**
63 | * Determines whether the given object's x/y/z values are equal to this Point3 object.
64 | *
65 | * @method Point3#equals
66 | * @param {Point3|any} a - The object to compare with this Point3.
67 | * @return {boolean} A value of true if the x and y points are equal, otherwise false.
68 | */
69 | equals(a) {
70 | return (a.x === this.x && a.y === this.y && a.z === this.z);
71 | }
72 |
73 | /**
74 | * Sets the x, y and z values of this Point3 object to the given values.
75 | * If you omit the y and z value then the x value will be applied to all three, for example:
76 | * `Point3.set(2)` is the same as `Point3.set(2, 2, 2)`
77 | * If however you set both x and y, but no z, the z value will be set to 0.
78 | *
79 | * @method Point3#set
80 | * @param {number} x - The x value of this point.
81 | * @param {number} [y] - The y value of this point. If not given the x value will be used in its place.
82 | * @param {number} [z] - The z value of this point. If not given and the y value is also not given, the x value will be used in its place.
83 | * @return {Point3} This Point3 object. Useful for chaining method calls.
84 | */
85 | set(x, y, z) {
86 | this.x = x || 0;
87 | this.y = y || ((y !== 0) ? this.x : 0);
88 | this.z = z || ((typeof y === 'undefined') ? this.x : 0);
89 | }
90 |
91 | /**
92 | * Sets the x, y and z values of this Point3 object to the given values.
93 | * If you omit the y and z value then the x value will be applied to all three, for example:
94 | * `Point3.setTo(2)` is the same as `Point3.setTo(2, 2, 2)`
95 | * If however you set both x and y, but no z, the z value will be set to 0.
96 | *
97 | * @method Point3#setTo
98 | * @param {number} x - The x value of this point.
99 | * @param {number} [y] - The y value of this point. If not given the x value will be used in its place.
100 | * @param {number} [z] - The z value of this point. If not given and the y value is also not given, the x value will be used in its place.
101 | * @return {Point3} This Point3 object. Useful for chaining method calls.
102 | */
103 | setTo(x, y, z) {
104 | return this.set(x, y, z);
105 | }
106 |
107 | /**
108 | * Adds the given x, y and z values to this Point3.
109 | *
110 | * @method Point3#add
111 | * @param {number} x - The value to add to Point3.x.
112 | * @param {number} y - The value to add to Point3.y.
113 | * @param {number} z - The value to add to Point3.z.
114 | * @return {Point3} This Point3 object. Useful for chaining method calls.
115 | */
116 | add(x, y, z) {
117 | this.x += x || 0;
118 | this.y += y || 0;
119 | this.z += z || 0;
120 |
121 | return this;
122 | }
123 |
124 | /**
125 | * Subtracts the given x, y and z values from this Point3.
126 | *
127 | * @method Point3#subtract
128 | * @param {number} x - The value to subtract from Point3.x.
129 | * @param {number} y - The value to subtract from Point3.y.
130 | * @param {number} z - The value to subtract from Point3.z.
131 | * @return {Point3} This Point3 object. Useful for chaining method calls.
132 | */
133 | subtract(x, y, z) {
134 | this.x -= x || 0;
135 | this.y -= y || 0;
136 | this.z -= z || 0;
137 |
138 | return this;
139 | }
140 |
141 | /**
142 | * Multiplies Point3.x, Point3.y and Point3.z by the given x and y values. Sometimes known as `Scale`.
143 | *
144 | * @method Point3#multiply
145 | * @param {number} x - The value to multiply Point3.x by.
146 | * @param {number} y - The value to multiply Point3.y by.
147 | * @param {number} z - The value to multiply Point3.z by.
148 | * @return {Point3} This Point3 object. Useful for chaining method calls.
149 | */
150 | multiply(x, y, z) {
151 | this.x *= x || 1;
152 | this.y *= y || 1;
153 | this.z *= z || 1;
154 |
155 | return this;
156 | }
157 |
158 | /**
159 | * Divides Point3.x, Point3.y and Point3.z by the given x, y and z values.
160 | *
161 | * @method Point3#divide
162 | * @param {number} x - The value to divide Point3.x by.
163 | * @param {number} y - The value to divide Point3.y by.
164 | * @param {number} z - The value to divide Point3.z by.
165 | * @return {Point3} This Point3 object. Useful for chaining method calls.
166 | */
167 | divide(x, y, z) {
168 | this.x /= x || 1;
169 | this.y /= y || 1;
170 | this.z /= z || 1;
171 |
172 | return this;
173 | }
174 |
175 | /**
176 | * Adds the coordinates of two points together to create a new point.
177 | *
178 | * @method Point3.add
179 | * @param {Point3} a - The first Point3 object.
180 | * @param {Point3} b - The second Point3 object.
181 | * @param {Point3} [out] - Optional Point3 to store the value in, if not supplied a new Point3 object will be created.
182 | * @return {Point3} The new Point3 object.
183 | */
184 | static add(a, b, out = new Point3()) {
185 | out.x = a.x + b.x;
186 | out.y = a.y + b.y;
187 | out.z = a.z + b.z;
188 |
189 | return out;
190 | }
191 |
192 | /**
193 | * Subtracts the coordinates of two points to create a new point.
194 | *
195 | * @method Point3.subtract
196 | * @param {Point3} a - The first Point3 object.
197 | * @param {Point3} b - The second Point3 object.
198 | * @param {Point3} [out] - Optional Point3 to store the value in, if not supplied a new Point3 object will be created.
199 | * @return {Point3} The new Point3 object.
200 | */
201 | static subtract(a, b, out = new Point3()) {
202 | out.x = a.x - b.x;
203 | out.y = a.y - b.y;
204 | out.z = a.z - b.z;
205 |
206 | return out;
207 | }
208 |
209 | /**
210 | * Multiplies the coordinates of two points to create a new point.
211 | *
212 | * @method Point3.multiply
213 | * @param {Point3} a - The first Point3 object.
214 | * @param {Point3} b - The second Point3 object.
215 | * @param {Point3} [out] - Optional Point3 to store the value in, if not supplied a new Point3 object will be created.
216 | * @return {Point3} The new Point3 object.
217 | */
218 | static multiply(a, b, out = new Point3()) {
219 | out.x = a.x * b.x;
220 | out.y = a.y * b.y;
221 | out.z = a.z * b.z;
222 |
223 | return out;
224 | }
225 |
226 | /**
227 | * Divides the coordinates of two points to create a new point.
228 | *
229 | * @method Point3.divide
230 | * @param {Point3} a - The first Point3 object.
231 | * @param {Point3} b - The second Point3 object.
232 | * @param {Point3} [out] - Optional Point3 to store the value in, if not supplied a new Point3 object3 will be created.
233 | * @return {Point3} The new Point3 object.
234 | */
235 | static divide (a, b, out = new Point3()) {
236 | out.x = a.x / b.x;
237 | out.y = a.y / b.y;
238 | out.z = a.z / b.z;
239 |
240 | return out;
241 | }
242 |
243 | /**
244 | * Determines whether the two given Point3 objects are equal. They are considered equal if they have the same x, y and z values.
245 | *
246 | * @method Point3.equals
247 | * @param {Point3} a - The first Point3 object.
248 | * @param {Point3} b - The second Point3 object.
249 | * @return {boolean} A value of true if the Points3 are equal, otherwise false.
250 | */
251 | static equals(a, b) {
252 | return (a.x === b.x && a.y === b.y && a.z === b.z);
253 | }
254 | }
255 |
256 | export default Point3;
257 |
--------------------------------------------------------------------------------
/src/IsoSprite.js:
--------------------------------------------------------------------------------
1 | import Point3 from './Point3';
2 | import Cube from './Cube';
3 |
4 | export const ISOSPRITE = 'IsoSprite';
5 | const Sprite = Phaser.GameObjects.Sprite;
6 |
7 | /**
8 | * @class IsoSprite
9 | *
10 | * @classdesc
11 | * Create a new `IsoSprite` object. IsoSprites are extended versions of standard Sprites that are suitable for axonometric positioning.
12 | *
13 | * IsoSprites are simply Sprites that have three new position properties (isoX, isoY and isoZ) and ask the instance of Projector what their position should be in a 2D scene whenever these properties are changed.
14 | * The IsoSprites retain their 2D position property to prevent any problems and allow you to interact with them as you would a normal Sprite. The upside of this simplicity is that things should behave predictably for those already used to Phaser.
15 | */
16 | export default class IsoSprite extends Sprite {
17 | /**
18 | * @constructor
19 | * @extends Phaser.GameObjects.Sprite
20 | * @param {Phaser.Scene} scene - A reference to the current scene.
21 | * @param {number} x - The x coordinate (in 3D space) to position the IsoSprite at.
22 | * @param {number} y - The y coordinate (in 3D space) to position the IsoSprite at.
23 | * @param {number} z - The z coordinate (in 3D space) to position the IsoSprite at.
24 | * @param {string|Phaser.RenderTexture|Phaser.BitmapData} key - This is the image or texture used by the IsoSprite during rendering. It can be a string which is a reference to the Cache entry, or an instance of a RenderTexture or PIXI.Texture.
25 | * @param {string|number} frame - If this IsoSprite is using part of a sprite sheet or texture atlas you can specify the exact frame to use by giving a string or numeric index.
26 | */
27 | constructor(scene, x, y, z, texture, frame) {
28 | super(scene, x, y, texture, frame);
29 |
30 | /**
31 | * @property {number} type - The const type of this object.
32 | * @readonly
33 | */
34 | this.type = ISOSPRITE;
35 |
36 | /**
37 | * @property {Point3} _isoPosition - Internal 3D position.
38 | * @private
39 | */
40 | this._isoPosition = new Point3(x, y, z);
41 |
42 | /**
43 | * @property {number} snap - Snap this IsoSprite's position to the specified value; handy for keeping pixel art snapped to whole pixels.
44 | * @default
45 | */
46 | this.snap = 0;
47 |
48 | /**
49 | * @property {boolean} _isoPositionChanged - Internal invalidation control for positioning.
50 | * @readonly
51 | * @private
52 | */
53 | this._isoPositionChanged = true;
54 |
55 | /**
56 | * @property {boolean} _isoBoundsChanged - Internal invalidation control for isometric bounds.
57 | * @readonly
58 | * @private
59 | */
60 | this._isoBoundsChanged = true;
61 |
62 | this._project();
63 |
64 | /**
65 | * @property {Cube} _isoBounds - Internal derived 3D bounds.
66 | * @private
67 | */
68 | this._isoBounds = this.resetIsoBounds();
69 | }
70 |
71 | /**
72 | * The axonometric position of the IsoSprite on the x axis. Increasing the x coordinate will move the object down and to the right on the screen.
73 | *
74 | * @name IsoSprite#isoX
75 | * @property {number} isoX - The axonometric position of the IsoSprite on the x axis.
76 | */
77 | get isoX() {
78 | return this._isoPosition.x;
79 | }
80 |
81 | set isoX(value) {
82 | this._isoPosition.x = value;
83 | this._isoPositionChanged = this._isoBoundsChanged = true;
84 | if (this.body){
85 | this.body._reset = true;
86 | }
87 | }
88 |
89 | /**
90 | * The axonometric position of the IsoSprite on the y axis. Increasing the y coordinate will move the object down and to the left on the screen.
91 | *
92 | * @name IsoSprite#isoY
93 | * @property {number} isoY - The axonometric position of the IsoSprite on the y axis.
94 | */
95 | get isoY() {
96 | return this._isoPosition.y;
97 | }
98 |
99 | set isoY(value) {
100 | this._isoPosition.y = value;
101 | this._isoPositionChanged = this._isoBoundsChanged = true;
102 |
103 | if (this.body){
104 | this.body._reset = true;
105 | }
106 | }
107 |
108 | /**
109 | * The axonometric position of the IsoSprite on the z axis. Increasing the z coordinate will move the object directly upwards on the screen.
110 | *
111 | * @name Phaser.Plugin.Isometric.IsoSprite#isoZ
112 | * @property {number} isoZ - The axonometric position of the IsoSprite on the z axis.
113 | */
114 | get isoZ() {
115 | return this._isoPosition.z;
116 | }
117 |
118 | set isoZ(value) {
119 | this._isoPosition.z = value;
120 | this._isoPositionChanged = this._isoBoundsChanged = true;
121 | if (this.body){
122 | this.body._reset = true;
123 | }
124 | }
125 |
126 | /**
127 | * A Point3 object representing the axonometric position of the IsoSprite.
128 | *
129 | * @name Phaser.Plugin.Isometric.IsoSprite#isoPosition
130 | * @property {Point3} isoPosition - The axonometric position of the IsoSprite.
131 | * @readonly
132 | */
133 | get isoPosition() {
134 | return this._isoPosition;
135 | }
136 |
137 | /**
138 | * A Cube object representing the derived boundsof the IsoSprite.
139 | *
140 | * @name Phaser.Plugin.Isometric.IsoSprite#isoBounds
141 | * @property {Point3} isoBounds - The derived 3D bounds of the IsoSprite.
142 | * @readonly
143 | */
144 | get isoBounds() {
145 | if (this._isoBoundsChanged || !this._isoBounds) {
146 | this.resetIsoBounds();
147 | this._isoBoundsChanged = false;
148 | }
149 |
150 | return this._isoBounds;
151 | }
152 |
153 | /**
154 | * Internal function that performs the axonometric projection from 3D to 2D space.
155 | * @method Phaser.Plugin.Isometric.IsoSprite#_project
156 | * @memberof Phaser.Plugin.Isometric.IsoSprite
157 | * @private
158 | */
159 | _project() {
160 | if (this._isoPositionChanged) {
161 | const pluginKey = this.scene.sys.settings.map.isoPlugin;
162 | const sceneProjector = this.scene[pluginKey].projector;
163 | const { x, y } = sceneProjector.project(this._isoPosition);
164 |
165 | this.x = x;
166 | this.y = y;
167 | this.depth = (this._isoPosition.x + this._isoPosition.y) + (this._isoPosition.z * 1.25);
168 |
169 | if (this.snap > 0) {
170 | this.x = Phaser.Math.snapTo(this.x, this.snap);
171 | this.y = Phaser.Math.snapTo(this.y, this.snap);
172 | }
173 |
174 | this._isoPositionChanged = this._isoBoundsChanged = true;
175 | }
176 | }
177 |
178 | /**
179 | * Internal function called by the World update cycle.
180 | *
181 | * @method IsoSprite#preUpdate
182 | * @memberof IsoSprite
183 | */
184 | preUpdate(time, delta) {
185 | Sprite.prototype.preUpdate.call(this, time, delta);
186 |
187 | this._project();
188 | }
189 |
190 | resetIsoBounds() {
191 | if (typeof this._isoBounds === 'undefined') {
192 | this._isoBounds = new Cube();
193 | }
194 |
195 | var asx = Math.abs(this.scaleX);
196 | var asy = Math.abs(this.scaleY);
197 |
198 | this._isoBounds.widthX = Math.round(Math.abs(this.width) * 0.5) * asx;
199 | this._isoBounds.widthY = Math.round(Math.abs(this.width) * 0.5) * asx;
200 | this._isoBounds.height = Math.round(Math.abs(this.height) - (Math.abs(this.width) * 0.5)) * asy;
201 |
202 | this._isoBounds.x = this.isoX + (this._isoBounds.widthX * -this.originX) + this._isoBounds.widthX * 0.5;
203 | this._isoBounds.y = this.isoY + (this._isoBounds.widthY * this.originX) - this._isoBounds.widthY * 0.5;
204 | this._isoBounds.z = this.isoZ - (Math.abs(this.height) * (1 - this.originY)) + (Math.abs(this.width * 0.5));
205 |
206 | return this._isoBounds;
207 | }
208 | }
209 |
210 |
211 |
212 |
213 | // Phaser.Utils.Debug.prototype.isoSprite = function (sprite, color, filled) {
214 | //
215 | // if (!sprite.isoBounds) {
216 | // return;
217 | // }
218 | //
219 | // if (typeof filled === 'undefined') {
220 | // filled = true;
221 | // }
222 | //
223 | // color = color || 'rgba(0,255,0,0.4)';
224 | //
225 | //
226 | // var points = [],
227 | // corners = sprite.isoBounds.getCorners();
228 | //
229 | // var posX = -sprite.game.camera.x;
230 | // var posY = -sprite.game.camera.y;
231 | //
232 | // this.start();
233 | //
234 | // if (filled) {
235 | // points = [corners[1], corners[3], corners[2], corners[6], corners[4], corners[5], corners[1]];
236 | //
237 | // points = points.map(function (p) {
238 | // var newPos = sprite.game.iso.project(p);
239 | // newPos.x += posX;
240 | // newPos.y += posY;
241 | // return newPos;
242 | // });
243 | // this.context.beginPath();
244 | // this.context.fillStyle = color;
245 | // this.context.moveTo(points[0].x, points[0].y);
246 | //
247 | // for (var i = 1; i < points.length; i++) {
248 | // this.context.lineTo(points[i].x, points[i].y);
249 | // }
250 | // this.context.fill();
251 | // } else {
252 | // points = corners.slice(0, corners.length);
253 | // points = points.map(function (p) {
254 | // var newPos = sprite.game.iso.project(p);
255 | // newPos.x += posX;
256 | // newPos.y += posY;
257 | // return newPos;
258 | // });
259 | //
260 | // this.context.moveTo(points[0].x, points[0].y);
261 | // this.context.beginPath();
262 | // this.context.strokeStyle = color;
263 | //
264 | // this.context.lineTo(points[1].x, points[1].y);
265 | // this.context.lineTo(points[3].x, points[3].y);
266 | // this.context.lineTo(points[2].x, points[2].y);
267 | // this.context.lineTo(points[6].x, points[6].y);
268 | // this.context.lineTo(points[4].x, points[4].y);
269 | // this.context.lineTo(points[5].x, points[5].y);
270 | // this.context.lineTo(points[1].x, points[1].y);
271 | // this.context.lineTo(points[0].x, points[0].y);
272 | // this.context.lineTo(points[4].x, points[4].y);
273 | // this.context.moveTo(points[0].x, points[0].y);
274 | // this.context.lineTo(points[2].x, points[2].y);
275 | // this.context.moveTo(points[3].x, points[3].y);
276 | // this.context.lineTo(points[7].x, points[7].y);
277 | // this.context.lineTo(points[6].x, points[6].y);
278 | // this.context.moveTo(points[7].x, points[7].y);
279 | // this.context.lineTo(points[5].x, points[5].y);
280 | // this.context.stroke();
281 | // this.context.closePath();
282 | // }
283 | //
284 | // this.stop();
285 | //
286 | // };
287 |
--------------------------------------------------------------------------------
/src/Octree.js:
--------------------------------------------------------------------------------
1 | import Cube from './Cube';
2 |
3 | /**
4 | * @class Octree
5 | *
6 | * @classdesc A Octree implementation based on Phaser.QuadTree.
7 | * Original version at https://github.com/timohausmann/quadtree-js/
8 | */
9 | class Octree {
10 | /**
11 | * @constructor
12 | * @param {number} x - The bottom-back coordinate of the octree.
13 | * @param {number} y - The bottom-back coordinate of the octree.
14 | * @param {number} z - The bottom-back coordinate of the octree.
15 | * @param {number} widthX - The width X (breadth) of the octree.
16 | * @param {number} widthY - The width Y (depth) of the octree.
17 | * @param {number} height - The height (Z) of the octree.
18 | * @param {number} [maxObjects=10] - The maximum number of objects per node.
19 | * @param {number} [maxLevels=4] - The maximum number of levels to iterate to.
20 | * @param {number} [level=0] - Which level is this?
21 | */
22 | constructor(x, y, z, widthX, widthY, height, maxObjects, maxLevels, level) {
23 | /**
24 | * @property {number} maxObjects - The maximum number of objects per node.
25 | * @default
26 | */
27 | this.maxObjects = 10;
28 |
29 | /**
30 | * @property {number} maxLevels - The maximum number of levels to break down to.
31 | * @default
32 | */
33 | this.maxLevels = 4;
34 |
35 | /**
36 | * @property {number} level - The current level.
37 | */
38 | this.level = 0;
39 |
40 | /**
41 | * @property {object} bounds - Object that contains the octree bounds.
42 | */
43 | this.bounds = {};
44 |
45 | /**
46 | * @property {array} objects - Array of octree children.
47 | */
48 | this.objects = [];
49 |
50 | /**
51 | * @property {array} nodes - Array of associated child nodes.
52 | */
53 | this.nodes = [];
54 |
55 | /**
56 | * @property {array} _empty - Internal empty array.
57 | * @private
58 | */
59 | this._empty = [];
60 |
61 | this.reset(x, y, z, widthX, widthY, height, maxObjects, maxLevels, level);
62 | }
63 |
64 | /**
65 | * Resets the QuadTree.
66 | *
67 | * @method Octree#reset
68 | * @param {number} x - The bottom-back coordinate of the octree.
69 | * @param {number} y - The bottom-back coordinate of the octree.
70 | * @param {number} z - The bottom-back coordinate of the octree.
71 | * @param {number} widthX - The width X (breadth) of the octree.
72 | * @param {number} widthY - The width Y (depth) of the octree.
73 | * @param {number} height - The height (Z) of the octree.
74 | * @param {number} [maxObjects=10] - The maximum number of objects per node.
75 | * @param {number} [maxLevels=4] - The maximum number of levels to iterate to.
76 | * @param {number} [level=0] - Which level is this?
77 | */
78 | reset(x, y, z, widthX, widthY, height, maxObjects, maxLevels, level) {
79 | this.maxObjects = maxObjects || 10;
80 | this.maxLevels = maxLevels || 4;
81 | this.level = level || 0;
82 |
83 | this.bounds = {
84 | x: Math.round(x),
85 | y: Math.round(y),
86 | z: Math.round(z),
87 | widthX: widthX,
88 | widthY: widthY,
89 | height: height,
90 | subWidthX: Math.floor(widthX * 0.5),
91 | subWidthY: Math.floor(widthY * 0.5),
92 | subHeight: Math.floor(height * 0.5),
93 | frontX: Math.round(x) + Math.floor(widthX * 0.5),
94 | frontY: Math.round(y) + Math.floor(widthY * 0.5),
95 | top: Math.round(z) + Math.floor(height * 0.5)
96 | };
97 |
98 | this.objects.length = 0;
99 | this.nodes.length = 0;
100 | }
101 |
102 | /**
103 | * Populates this octree with the children of the given Group. In order to be added the child must exist and have a body property.
104 | *
105 | * @method Octree#populate
106 | * @param {Phaser.Group} group - The Group to add to the octree.
107 | */
108 | populate(group) {
109 | const len = group.children.size;
110 | const children = group.children.entries;
111 |
112 | for(let i = 0; i < len; i++) {
113 | this.populateHandler(children[i]);
114 | }
115 | }
116 |
117 | /**
118 | * Handler for the populate method.
119 | *
120 | * @method Octree#populateHandler
121 | * @param {IsoSprite|object} sprite - The Sprite to check.
122 | */
123 | populateHandler(sprite) {
124 | if (sprite.body) {
125 | this.insert(sprite.body);
126 | }
127 | }
128 |
129 | /**
130 | * Split the node into 8 subnodes
131 | *
132 | * @method Octree#split
133 | */
134 | split() {
135 | // bottom four octants
136 | // -x-y-z
137 | this.nodes[0] = new Octree(this.bounds.x, this.bounds.y, this.bounds.z, this.bounds.subWidthX, this.bounds.subWidthY, this.bounds.subHeight, this.maxLevels, (this.level + 1));
138 | // +x-y-z
139 | this.nodes[1] = new Octree(this.bounds.frontX, this.bounds.y, this.bounds.z, this.bounds.subWidthX, this.bounds.subWidthY, this.bounds.subHeight, this.maxLevels, (this.level + 1));
140 | // -x+y-z
141 | this.nodes[2] = new Octree(this.bounds.x, this.bounds.frontY, this.bounds.z, this.bounds.subWidthX, this.bounds.subWidthY, this.bounds.subHeight, this.maxLevels, (this.level + 1));
142 | // +x+y-z
143 | this.nodes[3] = new Octree(this.bounds.frontX, this.bounds.frontY, this.bounds.z, this.bounds.subWidthX, this.bounds.subWidthY, this.bounds.subHeight, this.maxLevels, (this.level + 1));
144 |
145 | // top four octants
146 | // -x-y+z
147 | this.nodes[4] = new Octree(this.bounds.x, this.bounds.y, this.bounds.top, this.bounds.subWidthX, this.bounds.subWidthY, this.bounds.subHeight, this.maxLevels, (this.level + 1));
148 | // +x-y+z
149 | this.nodes[5] = new Octree(this.bounds.frontX, this.bounds.y, this.bounds.top, this.bounds.subWidthX, this.bounds.subWidthY, this.bounds.subHeight, this.maxLevels, (this.level + 1));
150 | // -x+y+z
151 | this.nodes[6] = new Octree(this.bounds.x, this.bounds.frontY, this.bounds.top, this.bounds.subWidthX, this.bounds.subWidthY, this.bounds.subHeight, this.maxLevels, (this.level + 1));
152 | // +x+y+z
153 | this.nodes[7] = new Octree(this.bounds.frontX, this.bounds.frontY, this.bounds.top, this.bounds.subWidthX, this.bounds.subWidthY, this.bounds.subHeight, this.maxLevels, (this.level + 1));
154 | }
155 |
156 | /**
157 | * Insert the object into the node. If the node exceeds the capacity, it will split and add all objects to their corresponding subnodes.
158 | *
159 | * @method Octree#insert
160 | * @param {Body|Cube|object} body - The Body object to insert into the octree. Can be any object so long as it exposes x, y, z, frontX, frontY and top properties.
161 | */
162 | insert(body) {
163 | var i = 0;
164 | var index;
165 |
166 | // if we have subnodes ...
167 | if (this.nodes[0] != null) {
168 | index = this.getIndex(body);
169 |
170 | if (index != -1) {
171 | this.nodes[index].insert(body);
172 | return;
173 | }
174 | }
175 |
176 | this.objects.push(body);
177 |
178 | if (this.objects.length > this.maxObjects && this.level < this.maxLevels) {
179 | // Split if we don't already have subnodes
180 | if (this.nodes[0] == null) {
181 | this.split();
182 | }
183 |
184 | // Add objects to subnodes
185 | while (i < this.objects.length) {
186 | index = this.getIndex(this.objects[i]);
187 |
188 | if (index != -1) {
189 | // this is expensive - see what we can do about it
190 | this.nodes[index].insert(this.objects.splice(i, 1)[0]);
191 | } else {
192 | i++;
193 | }
194 | }
195 | }
196 | }
197 |
198 | /**
199 | * Determine which node the object belongs to.
200 | *
201 | * @method Octree#getIndex
202 | * @param {Cube|object} cube - The bounds in which to check.
203 | * @return {number} index - Index of the subnode (0-7), or -1 if cube cannot completely fit within a subnode and is part of the parent node.
204 | */
205 | getIndex(cube) {
206 | // default is that cube doesn't fit, i.e. it straddles the internal octants
207 | var index = -1;
208 |
209 | if (cube.x < this.bounds.frontX && cube.frontX < this.bounds.frontX) {
210 | if (cube.y < this.bounds.frontY && cube.frontY < this.bounds.frontY) {
211 | if (cube.z < this.bounds.top && cube.top < this.bounds.top) {
212 | // cube fits into -x-y-z octant
213 | index = 0;
214 | } else if (cube.z > this.bounds.top) {
215 | // cube fits into -x-y+z octant
216 | index = 4;
217 | }
218 | } else if (cube.y > this.bounds.frontY) {
219 | if (cube.z < this.bounds.top && cube.top < this.bounds.top) {
220 | // cube fits into -x+y-z octant
221 | index = 2;
222 | } else if (cube.z > this.bounds.top) {
223 | // cube fits into -x+y+z octant
224 | index = 6;
225 | }
226 | }
227 | } else if (cube.x > this.bounds.frontX) {
228 | if (cube.y < this.bounds.frontY && cube.frontY < this.bounds.frontY) {
229 | if (cube.z < this.bounds.top && cube.top < this.bounds.top) {
230 | // cube fits into +x-y-z octant
231 | index = 1;
232 | } else if (cube.z > this.bounds.top) {
233 | // cube fits into +x-y+z octant
234 | index = 5;
235 | }
236 | } else if (cube.y > this.bounds.frontY) {
237 | if (cube.z < this.bounds.top && cube.top < this.bounds.top) {
238 | // cube fits into +x+y-z octant
239 | index = 3;
240 | } else if (cube.z > this.bounds.top) {
241 | // cube fits into +x+y+z octant
242 | index = 7;
243 | }
244 | }
245 | }
246 |
247 | return index;
248 | }
249 |
250 | /**
251 | * Return all objects that could collide with the given IsoSprite or Cube.
252 | *
253 | * @method Octree#retrieve
254 | * @param {IsoSprite|Cube} source - The source object to check the Octree against. Either a IsoSprite or Cube.
255 | * @return {array} - Array with all detected objects.
256 | */
257 | retrieve(source) {
258 | var returnObjects, index;
259 |
260 | if (source instanceof Cube) {
261 | returnObjects = this.objects;
262 |
263 | index = this.getIndex(source);
264 | } else {
265 | if (!source.body) {
266 | return this._empty;
267 | }
268 |
269 | returnObjects = this.objects;
270 |
271 | index = this.getIndex(source.body);
272 | }
273 |
274 | if (this.nodes[0]) {
275 | // If cube fits into a subnode ..
276 | if (index !== -1) {
277 | returnObjects = returnObjects.concat(this.nodes[index].retrieve(source));
278 | } else {
279 | // If cube does not fit into a subnode, check it against all subnodes (unrolled for speed)
280 | returnObjects = returnObjects.concat(this.nodes[0].retrieve(source));
281 | returnObjects = returnObjects.concat(this.nodes[1].retrieve(source));
282 | returnObjects = returnObjects.concat(this.nodes[2].retrieve(source));
283 | returnObjects = returnObjects.concat(this.nodes[3].retrieve(source));
284 | returnObjects = returnObjects.concat(this.nodes[4].retrieve(source));
285 | returnObjects = returnObjects.concat(this.nodes[5].retrieve(source));
286 | returnObjects = returnObjects.concat(this.nodes[6].retrieve(source));
287 | returnObjects = returnObjects.concat(this.nodes[7].retrieve(source));
288 | }
289 | }
290 |
291 | return returnObjects;
292 | }
293 |
294 | /**
295 | * Clear the octree.
296 | * @method Octree#clear
297 | */
298 | clear() {
299 | this.objects.length = 0;
300 |
301 | var i = this.nodes.length;
302 |
303 | while (i--) {
304 | this.nodes[i].clear();
305 | this.nodes.splice(i, 1);
306 | }
307 |
308 | this.nodes.length = 0;
309 | }
310 | }
311 |
312 | /**
313 | * Visually renders an Octree to the display.
314 | *
315 | * @method Phaser.Utils.Debug#octree
316 | * @param {Octree} octree - The octree to render.
317 | * @param {string} color - The color of the lines in the quadtree.
318 | */
319 | // Phaser.Utils.Debug.prototype.octree = function (octree, color) {
320 | // color = color || 'rgba(255,0,0,0.3)';
321 | //
322 | // this.start();
323 | //
324 | // var bounds = octree.bounds,
325 | // i, points;
326 | //
327 | // if (octree.nodes.length === 0) {
328 | //
329 | // this.context.strokeStyle = color;
330 | //
331 | // var cube = new Cube(bounds.x, bounds.y, bounds.z, bounds.widthX, bounds.widthY, bounds.height);
332 | // var corners = cube.getCorners();
333 | //
334 | // var posX = -this.game.camera.x;
335 | // var posY = -this.game.camera.y;
336 | //
337 | // points = corners.slice(0, corners.length);
338 | //
339 | // points = points.map(function (p) {
340 | // var newPos = this.game.iso.project(p);
341 | // newPos.x += posX;
342 | // newPos.y += posY;
343 | // return newPos;
344 | // });
345 | //
346 | // this.context.moveTo(points[0].x, points[0].y);
347 | // this.context.beginPath();
348 | // this.context.strokeStyle = color;
349 | //
350 | // this.context.lineTo(points[1].x, points[1].y);
351 | // this.context.lineTo(points[3].x, points[3].y);
352 | // this.context.lineTo(points[2].x, points[2].y);
353 | // this.context.lineTo(points[6].x, points[6].y);
354 | // this.context.lineTo(points[4].x, points[4].y);
355 | // this.context.lineTo(points[5].x, points[5].y);
356 | // this.context.lineTo(points[1].x, points[1].y);
357 | // this.context.lineTo(points[0].x, points[0].y);
358 | // this.context.lineTo(points[4].x, points[4].y);
359 | // this.context.moveTo(points[0].x, points[0].y);
360 | // this.context.lineTo(points[2].x, points[2].y);
361 | // this.context.moveTo(points[3].x, points[3].y);
362 | // this.context.lineTo(points[7].x, points[7].y);
363 | // this.context.lineTo(points[6].x, points[6].y);
364 | // this.context.moveTo(points[7].x, points[7].y);
365 | // this.context.lineTo(points[5].x, points[5].y);
366 | // this.context.stroke();
367 | // this.context.closePath();
368 | //
369 | // for (i = 0; i < octree.objects.length; i++) {
370 | // this.body(octree.objects[i].sprite, 'rgb(0,255,0)', false);
371 | // }
372 | // } else {
373 | // for (i = 0; i < octree.nodes.length; i++) {
374 | // this.octree(octree.nodes[i]);
375 | // }
376 | // }
377 | //
378 | // this.stop();
379 | // };
380 |
381 | export default Octree;
382 |
--------------------------------------------------------------------------------
/src/physics/IsoPhysics.js:
--------------------------------------------------------------------------------
1 | import Point3 from '../Point3';
2 | import World from './World';
3 |
4 | /**
5 | * IsoPhysics Physics constructor.
6 | *
7 | * @class IsoPhysics
8 | * @classdesc IsoPhysics Physics
9 | */
10 | export default class IsoPhysics {
11 | /**
12 | * @constructor
13 | * @param {Phaser.Scene} Reference to the current scene instance.
14 | */
15 | constructor(scene) {
16 | /**
17 | * @property {Phaser.Scene} scene - Local reference to scene.
18 | */
19 | this.scene = scene;
20 |
21 | /**
22 | * @property {Projector} projector - Local reference to the current projector.
23 | */
24 | const pluginKey = this.scene.sys.settings.map.isoPlugin;
25 | this.projector = this.scene[pluginKey].projector;
26 |
27 | /**
28 | * @property {World} world - The physics World that gets created on start.
29 | */
30 | this.world = new World(this.scene);
31 | }
32 |
33 | /**
34 | * Find the distance between two display objects (like Sprites).
35 | *
36 | * @method Isometric.IsoPhysics#distanceBetween
37 | * @param {any} source - The Display Object to test from.
38 | * @param {any} target - The Display Object to test to.
39 | * @return {number} The distance between the source and target objects.
40 | */
41 | distanceBetween(source, target) {
42 | this._dx = source.x - target.x;
43 | this._dy = source.y - target.y;
44 | this._dz = source.z - target.z;
45 |
46 | return Math.sqrt(this._dx * this._dx + this._dy * this._dy + this._dz * this._dz);
47 | }
48 |
49 | /**
50 | * Find the distance between a display object (like a Sprite) and the given x/y coordinates only (ignore z).
51 | * The calculation is made from the display objects x/y coordinate. This may be the top-left if its anchor hasn't been changed.
52 | * If you need to calculate from the center of a display object instead use the method distanceBetweenCenters()
53 | *
54 | * @method IsoPhysics#distanceToXY
55 | * @param {any} displayObject - The Display Object to test from.
56 | * @param {number} x - The x coordinate to test to.
57 | * @param {number} y - The y coordinate to test to.
58 | * @return {number} The distance between the object and the x/y coordinates.
59 | */
60 | distanceToXY(displayObject, x, y) {
61 | this._dx = displayObject.x - x;
62 | this._dy = displayObject.y - y;
63 |
64 | return Math.sqrt(this._dx * this._dx + this._dy * this._dy);
65 | }
66 |
67 | /**
68 | * Find the distance between a display object (like a Sprite) and the given x/y/z coordinates.
69 | * The calculation is made from the display objects x/y/z coordinate. This may be the top-left if its anchor hasn't been changed.
70 | * If you need to calculate from the center of a display object instead use the method distanceBetweenCenters()
71 | *
72 | * @method IsoPhysics#distanceToXYZ
73 | * @param {any} displayObjectBody - The Display Object to test from.
74 | * @param {number} x - The x coordinate to test to.
75 | * @param {number} y - The y coordinate to test to.
76 | * @param {number} z - The y coordinate to test to
77 | * @return {number} The distance between the object and the x/y coordinates.
78 | */
79 | distanceToXYZ(displayObjectBody, x, y, z) {
80 | this._dx = displayObjectBody.x - x;
81 | this._dy = displayObjectBody.y - y;
82 | this._dz = displayObjectBody.z - z;
83 |
84 | return Math.sqrt(this._dx * this._dx + this._dy * this._dy + this._dz * this._dz);
85 | }
86 |
87 | /**
88 | * Find the distance between a display object (like a Sprite) and a Pointer. If no Pointer is given the Input.activePointer is used.
89 | * The calculation is made from the display objects x/y coordinate. This may be the top-left if its anchor hasn't been changed.
90 | * If you need to calculate from the center of a display object instead use the method distanceBetweenCenters()
91 | * The distance to the Pointer is returned in isometric distance.
92 | *
93 | * @method IsoPhysics#distanceToPointer
94 | * @param {any} displayObjectBody - The Display Object to test from.
95 | * @param {Phaser.Pointer} [pointer] - The Phaser.Pointer to test to. If none is given then Input.activePointer is used.
96 | * @return {number} The distance between the object and the Pointer.
97 | */
98 | distanceToPointer(displayObjectBody, pointer) {
99 | pointer = pointer || this.scene.input.activePointer;
100 | var isoPointer = this.projector.unproject(pointer.position, undefined, displayObjectBody.z);
101 | isoPointer.z = displayObjectBody.z;
102 | var a = this.anglesToXYZ(displayObjectBody, isoPointer.x, isoPointer.y, isoPointer.z);
103 |
104 | return a.r;
105 | }
106 |
107 | /**
108 | * Find the angles in radians between a display object (like a IsoSprite) and the given x/y/z coordinate.
109 | *
110 | * @method Phaser.Physics.Isometric.Isometric.IsoPhysics#anglesToXYZ
111 | * @param {any} displayObjectBody - The Display Object to test from.
112 | * @param {number} x - The x coordinate to get the angle to.
113 | * @param {number} y - The y coordinate to get the angle to.
114 | * @param {number} z - The z coordinate to get the angle to.
115 | * @return {number} The angle in radians between displayObjectBody.x/y to Pointer.x/y
116 | */
117 | anglesToXYZ(displayObjectBody, x, y, z) {
118 | // Spherical polar coordinates
119 | var r = this.distanceToXYZ(displayObjectBody, x, y, z);
120 | var theta = Math.atan2(y - displayObjectBody.y, x - displayObjectBody.x);
121 | var phi = Math.acos((z - displayObjectBody.z)/ r);
122 |
123 | return {r:r,theta:theta,phi:phi};
124 | }
125 |
126 | /**
127 | * Find the angle in radians between a display object (like a Sprite) and a Pointer, taking their x/y and center into account.
128 | * This is not the visual angle but the angle in the isometric co-ordinate system.
129 | *
130 | * @method Phaser.Physics.Isometric.IsoPhysics#angleToPointer
131 | * @param {any} displayObjectBody - The Display Object to test from.
132 | * @param {Phaser.Pointer} [pointer] - The Phaser.Pointer to test to. If none is given then Input.activePointer is used.
133 | * @return {number} The (isometric) angle in radians between displayObjectBody.x/y to Pointer.x/y.
134 | */
135 | angleToPointer(displayObjectBody, pointer) {
136 | pointer = pointer || this.scene.input.activePointer;
137 | var isoPointer = this.projector.unproject(pointer.position, undefined, displayObjectBody.z);
138 | isoPointer.z = displayObjectBody.z;
139 | var a = this.anglesToXYZ(displayObjectBody, isoPointer.x, isoPointer.y, isoPointer.z);
140 |
141 | return a.theta;
142 | }
143 |
144 | /**
145 | * Given the angle (in degrees) and speed calculate the velocity and return it as a Point object, or set it to the given point object.
146 | * One way to use this is: velocityFromAngle(angle, 200, sprite.velocity) which will set the values directly to the sprites velocity and not create a new Point object.
147 | *
148 | * @method Phaser.Physics.IsoPhysics#velocityFromAngle
149 | * @param {number} theta - The angle in radians for x,y in the isometric co-ordinate system
150 | * @param {number} [phi=Math.PI/2] - The angle in radians for z in the isometric co-ordinate system
151 | * @param {number} [speed=60] - The speed it will move, in pixels per second sq.
152 | * @param {Phaser.Point|object} [point] - The Point object in which the x and y properties will be set to the calculated velocity.
153 | * @return {Point3} - A Point where point.x contains the velocity x value and so on for y and z.
154 | */
155 | velocityFromAngles(theta, phi, speed) {
156 | if (phi === undefined) { phi = Math.sin(Math.PI/2); }
157 | if (speed === undefined) { speed = 60; }
158 |
159 | return new Point3(
160 | Math.cos(theta) * Math.sin(phi) * speed,
161 | Math.sin(theta) * Math.sin(phi) * speed,
162 | Math.cos(phi) * speed
163 | );
164 | }
165 |
166 | /**
167 | * Sets the acceleration.x/y property on the display object so it will move towards the x/y coordinates at the given speed (in pixels per second sq.)
168 | * You must give a maximum speed value, beyond which the display object won't go any faster.
169 | * Note: The display object does not continuously track the target. If the target changes location during transit the display object will not modify its course.
170 | * Note: The display object doesn't stop moving once it reaches the destination coordinates.
171 | *
172 | * @method Phaser.Physics.Isometric.IsoPhysics#accelerateToXYZ
173 | * @param {any} displayObject - The display object to move.
174 | * @param {number} x - The x coordinate to accelerate towards.
175 | * @param {number} y - The y coordinate to accelerate towards.
176 | * @param {number} z - The z coordinate to accelerate towards.
177 | * @param {number} [speed=60] - The speed it will accelerate in pixels per second.
178 | * @param {number} [xSpeedMax=500] - The maximum x velocity the display object can reach.
179 | * @param {number} [ySpeedMax=500] - The maximum y velocity the display object can reach.
180 | * @param {number} [zSpeedMax=500] - The maximum z velocity the display object can reach.
181 | * @return {number} The angle (in radians).
182 | */
183 | accelerateToXYZ(displayObject, x, y, z, speed, xSpeedMax, ySpeedMax, zSpeedMax) {
184 | if (speed === undefined) { speed = 60; }
185 | if (xSpeedMax === undefined) { xSpeedMax = 500; }
186 | if (ySpeedMax === undefined) { ySpeedMax = 500; }
187 | if (zSpeedMax === undefined) { zSpeedMax = 500; }
188 |
189 | var a = this.anglesToXYZ(displayObject.body, x, y,z);
190 | var v = this.velocityFromAngles(a.theta,a.phi,speed);
191 |
192 | displayObject.body.acceleration.setTo(v.x,v.y,v.z);
193 | displayObject.body.maxVelocity.setTo(xSpeedMax, ySpeedMax, zSpeedMax);
194 |
195 | return a.theta;
196 | }
197 |
198 | /**
199 | * Move the given display object towards the x/y coordinates at a steady velocity.
200 | * If you specify a maxTime then it will adjust the speed (over-writing what you set) so it arrives at the destination in that number of seconds.
201 | * Timings are approximate due to the way browser timers work. Allow for a variance of +- 50ms.
202 | * Note: The display object does not continuously track the target. If the target changes location during transit the display object will not modify its course.
203 | * Note: The display object doesn't stop moving once it reaches the destination coordinates.
204 | * Note: Doesn't take into account acceleration, maxVelocity or drag (if you've set drag or acceleration too high this object may not move at all)
205 | *
206 | * @method Phaser.Physics.Isometric.IsoPhysics#moveToXYZ
207 | * @param {any} displayObject - The display object to move, must have an isoArcade body.
208 | * @param {number} x - The x coordinate to move towards.
209 | * @param {number} y - The y coordinate to move towards.
210 | * @param {number} z - The z coordinate to move towards.
211 | * @param {number} [speed=60] - The speed it will move, in pixels per second (default is 60 pixels/sec)
212 | * @param {number} [maxTime=0] - Time given in milliseconds (1000 = 1 sec). If set the speed is adjusted so the object will arrive at destination in the given number of ms.
213 | * @return {number} The angle (in radians).
214 | */
215 | moveToXYZ(displayObject, x, y, z, speed, maxTime) {
216 | if (typeof speed === 'undefined') {
217 | speed = 60;
218 | }
219 | if (typeof maxTime === 'undefined') {
220 | maxTime = 0;
221 | }
222 |
223 | if (maxTime > 0) {
224 | // We know how many pixels we need to move, but how fast?
225 | speed = this.distanceToXYZ(displayObject.body, x, y ,z) / (maxTime / 1000);
226 | }
227 | var a = this.anglesToXYZ(displayObject.body, x, y,z);
228 | var v = this.velocityFromAngles(a.theta,a.phi,speed);
229 |
230 | displayObject.body.velocity.copyFrom(v);
231 |
232 | return a.theta;
233 | }
234 |
235 | /**
236 | * Move the given display object towards the destination object at a steady velocity.
237 | * If you specify a maxTime then it will adjust the speed (overwriting what you set) so it arrives at the destination in that number of seconds.
238 | * Timings are approximate due to the way browser timers work. Allow for a variance of +- 50ms.
239 | * Note: The display object does not continuously track the target. If the target changes location during transit the display object will not modify its course.
240 | * Note: The display object doesn't stop moving once it reaches the destination coordinates.
241 | * Note: Doesn't take into account acceleration, maxVelocity or drag (if you've set drag or acceleration too high this object may not move at all)
242 | *
243 | * @method Phaser.Physics.Isometric.IsoPhysics#moveToObject
244 | * @param {any} displayObject - The display object to move.
245 | * @param {any} destination - The display object to move towards. Can be any object but must have visible x/y/z properties.
246 | * @param {number} [speed=60] - The speed it will move, in pixels per second (default is 60 pixels/sec)
247 | * @param {number} [maxTime=0] - Time given in milliseconds (1000 = 1 sec). If set the speed is adjusted so the object will arrive at destination in the given number of ms.
248 | * @return {number} The angle (in radians).
249 | */
250 | moveToObject(displayObject, destination, speed, maxTime) {
251 | return this.moveToXYZ(displayObject, destination.x, destination.y, destination.z, speed, maxTime);
252 | }
253 |
254 | /**
255 | * Move the given display object towards the pointer at a steady x & y velocity. If no pointer is given it will use Phaser.Input.activePointer.
256 | * If you specify a maxTime then it will adjust the speed (over-writing what you set) so it arrives at the destination in that number of seconds.
257 | * Timings are approximate due to the way browser timers work. Allow for a variance of +- 50ms.
258 | * Note: The display object does not continuously track the target. If the target changes location during transit the display object will not modify its course.
259 | * Note: The display object doesn't stop moving once it reaches the destination coordinates.
260 | *
261 | * @method Phaser.Physics.Isometric.IsoPhysics#moveToPointer
262 | * @param {any} displayObject - The display object to move.
263 | * @param {number} [speed=60] - The speed it will move, in pixels per second (default is 60 pixels/sec)
264 | * @param {Phaser.Pointer} [pointer] - The pointer to move towards. Defaults to Phaser.Input.activePointer.
265 | * @param {number} [maxTime=0] - Time given in milliseconds (1000 = 1 sec). If set the speed is adjusted so the object will arrive at destination in the given number of ms.
266 | * @return {number} The angle (in radians).
267 | */
268 | moveToPointer(displayObject, speed, pointer, maxTime) {
269 | pointer = pointer || this.game.input.activePointer;
270 | var isoPointer = this.game.iso.unproject(pointer.position,undefined,displayObject.body.z);
271 | isoPointer.z = displayObject.body.z;
272 |
273 | if (typeof speed === 'undefined') {
274 | speed = 60;
275 | }
276 | if (typeof maxTime === 'undefined') {
277 | maxTime = 0;
278 | }
279 |
280 | if (maxTime > 0) {
281 | // We know how many pixels we need to move, but how fast?
282 | speed = this.distanceToXYZ(displayObject.body, isoPointer.x, isoPointer.y ,isoPointer.z) / (maxTime / 1000);
283 | }
284 | var a = this.anglesToXYZ(displayObject.body, isoPointer.x, isoPointer.y,isoPointer.z);
285 | var v = this.velocityFromAngles(a.theta,a.phi,speed);
286 |
287 | displayObject.body.velocity.x=v.x;
288 | displayObject.body.velocity.y=v.y;
289 |
290 | return a.theta;
291 | }
292 |
293 | boot() {
294 | const eventEmitter = this.scene.sys.events;
295 |
296 | eventEmitter.on('update', this.world.update, this.world);
297 | eventEmitter.on('postupdate', this.world.postUpdate, this.world);
298 | }
299 |
300 | static register(PluginManager) {
301 | PluginManager.register('IsoPhysics', IsoPhysics, 'isoPhysics');
302 | }
303 | }
304 |
305 | //
306 | // Phaser.Physics.prototype.parseConfig = (function (_super) {
307 | //
308 | // return function () {
309 | // if (this.config.hasOwnProperty('isoArcade') && this.config['isoArcade'] === true && hasOwnProperty('IsoPhysics')) {
310 | // this.isoArcade = new Phaser.Plugin.Isometric(this.game, this.config);
311 | // }
312 | // return _super.call(this);
313 | // };
314 | //
315 | // })(Phaser.Physics.prototype.parseConfig);
316 | //
317 | // Phaser.Physics.prototype.startSystem = (function (_super) {
318 | //
319 | // return function (system) {
320 | // if (system === ISOARCADE && this.isoArcade === null) {
321 | // this.isoArcade = new IsoPhysics(this.game);
322 | // this.setBoundsToWorld();
323 | // }
324 | // return _super.call(this, system);
325 | // };
326 | //
327 | // })(Phaser.Physics.prototype.startSystem);
328 | //
329 | // Phaser.Physics.prototype.enable = (function (_super) {
330 | //
331 | // return function (sprite, system) {
332 | // if (system === ISOARCADE && this.isoArcade) {
333 | // this.isoArcade.enable(sprite);
334 | // }
335 | // return _super.call(this, sprite, system);
336 | // };
337 | //
338 | // })(Phaser.Physics.prototype.enable);
339 | //
340 | // Phaser.Physics.prototype.setBoundsToWorld = (function (_super) {
341 | //
342 | // return function () {
343 | // if (this.isoArcade) {
344 | // this.isoArcade.setBoundsToWorld();
345 | // }
346 | // return _super.call(this);
347 | // };
348 | //
349 | // })(Phaser.Physics.prototype.setBoundsToWorld);
350 | //
351 | // Phaser.Physics.prototype.destroy = (function (_super) {
352 | //
353 | // return function () {
354 | // this.isoArcade = null;
355 | //
356 | // return _super.call(this);
357 | // };
358 | //
359 | // })(Phaser.Physics.prototype.destroy);
360 |
--------------------------------------------------------------------------------
/src/Cube.js:
--------------------------------------------------------------------------------
1 | import Point3 from './Point3';
2 |
3 | /**
4 | * @class Cube
5 | *
6 | * @classdesc
7 | * Creates a new Cube object with the bottom-back corner specified by the x, y and z parameters, with the specified breadth (widthX), depth (widthY) and height parameters. If you call this function without parameters, a Cube with x, y, z, breadth, depth and height properties set to 0 is created.
8 | */
9 | class Cube {
10 | /**
11 | * @constructor
12 | * @param {number} x - The x coordinate of the bottom-back corner of the Cube.
13 | * @param {number} y - The y coordinate of the bottom-back corner of the Cube.
14 | * @param {number} z - The z coordinate of the bottom-back corner of the Cube.
15 | * @param {number} widthX - The X axis width (breadth) of the Cube. Should always be either zero or a positive value.
16 | * @param {number} widthY - The Y axis width (depth) of the Cube. Should always be either zero or a positive value.
17 | * @param {number} height - The Z axis height of the Cube. Should always be either zero or a positive value.
18 | * @return {Cube} This Cube object.
19 | */
20 | constructor(x = 0, y = 0, z = 0, widthX = 0, widthY = 0, height = 0) {
21 | /**
22 | * @property {number} x - The x coordinate of the bottom-back corner of the Cube.
23 | */
24 | this.x = x;
25 |
26 | /**
27 | * @property {number} y - The y coordinate of the bottom-back corner of the Cube.
28 | */
29 | this.y = y;
30 |
31 | /**
32 | * @property {number} z - The z coordinate of the bottom-back corner of the Cube.
33 | */
34 | this.z = z;
35 |
36 | /**
37 | * @property {number} widthX - The X axis width (breadth) of the Cube. This value should never be set to a negative.
38 | */
39 | this.widthX = widthX;
40 |
41 | /**
42 | * @property {number} widthY - The Y axis width (depth) of the Cube. This value should never be set to a negative.
43 | */
44 | this.widthY = widthY;
45 |
46 | /**
47 | * @property {number} height - The Z axis height of the Cube. This value should never be set to a negative.
48 | */
49 | this.height = height;
50 |
51 | /**
52 | * @property {Array.} _corners - The 8 corners of the Cube.
53 | * @private
54 | */
55 | this._corners = [
56 | new Point3(this.x, this.y, this.z),
57 | new Point3(this.x, this.y, this.z + this.height),
58 | new Point3(this.x, this.y + this.widthY, this.z),
59 | new Point3(this.x, this.y + this.widthY, this.z + this.height),
60 | new Point3(this.x + this.widthX, this.y, this.z),
61 | new Point3(this.x + this.widthX, this.y, this.z + this.height),
62 | new Point3(this.x + this.widthX, this.y + this.widthY, this.z),
63 | new Point3(this.x + this.widthX, this.y + this.widthY, this.z + this.height)
64 | ];
65 | }
66 |
67 | /**
68 | * Sets the members of Cube to the specified values.
69 | * @method Cube#setTo
70 | * @param {number} x - The x coordinate of the bottom-back corner of the Cube.
71 | * @param {number} y - The y coordinate of the bottom-back corner of the Cube.
72 | * @param {number} z - The z coordinate of the bottom-back corner of the Cube.
73 | * @param {number} widthX - The X axis width (breadth) of the Cube. This value should never be set to a negative.
74 | * @param {number} widthY - The Y axis width (depth) of the Cube. This value should never be set to a negative.
75 | * @param {number} height - The Z axis height of the Cube. This value should never be set to a negative.
76 | * @return {Cube} This Cube object
77 | */
78 | setTo(x, y, z, widthX, widthY, height) {
79 | this.x = x;
80 | this.y = y;
81 | this.z = z;
82 | this.widthX = widthX;
83 | this.widthY = widthY;
84 | this.height = height;
85 |
86 | return this;
87 | }
88 |
89 | /**
90 | * Copies the x, y, z, widthX, widthY and height properties from any given object to this Cube.
91 | * @method Cube#copyFrom
92 | * @param {any} source - The object to copy from.
93 | * @return {Cube} This Cube object.
94 | */
95 | copyFrom(source) {
96 | this.setTo(source.x, source.y, source.z, source.widthX, source.widthY, source.height);
97 | }
98 |
99 | /**
100 | * Copies the x, y, z, widthX, widthY and height properties from this Cube to any given object.
101 | * @method Cube#copyTo
102 | * @param {any} dest - The object to copy to.
103 | * @return {Cube} This Cube object.
104 | */
105 | copyTo(dest) {
106 | dest.x = this.x;
107 | dest.y = this.y;
108 | dest.z = this.z;
109 | dest.widthX = this.widthX;
110 | dest.widthY = this.widthY;
111 | dest.height = this.height;
112 |
113 | return dest;
114 | }
115 |
116 | /**
117 | * The size of the Cube object, expressed as a Point3 object with the values of the widthX, widthY and height properties.
118 | * @method Cube#size
119 | * @param {Point3} [output] - Optional Point3 object. If given the values will be set into the object, otherwise a brand new Point3 object will be created and returned.
120 | * @return {Point3} The size of the Cube object.
121 | */
122 | size(output) {
123 | return Cube.size(this, output);
124 | }
125 |
126 | /**
127 | * Determines whether the specified coordinates are contained within the region defined by this Cube object.
128 | * @method Cube#contains
129 | * @param {number} x - The x coordinate of the point to test.
130 | * @param {number} y - The y coordinate of the point to test.
131 | * @param {number} y - The z coordinate of the point to test.
132 | * @return {boolean} A value of true if the Cube object contains the specified point; otherwise false.
133 | */
134 | contains(x, y, z) {
135 | return Cube.contains(this, x, y, z);
136 | }
137 |
138 | /**
139 | * Determines whether the specified X and Y coordinates are contained within the region defined by this Cube object.
140 | * @method Cube#containsXY
141 | * @param {number} x - The x coordinate of the point to test.
142 | * @param {number} y - The y coordinate of the point to test.
143 | * @return {boolean} A value of true if this Cube object contains the specified point; otherwise false.
144 | */
145 | containsXY(x, y) {
146 | return Cube.containsXY(this, x, y);
147 | }
148 |
149 | /**
150 | * Returns a new Cube object with the same values for the x, y, z, widthX, widthY and height properties as the original Cube object.
151 | * @method Cube#clone
152 | * @param {Cube} [output] - Optional Cube object. If given the values will be set into the object, otherwise a brand new Cube object will be created and returned.
153 | * @return {Cube}
154 | */
155 | clone(output) {
156 | return Cube.clone(this, output);
157 | }
158 |
159 | /**
160 | * Determines whether the two Cubes intersect with each other.
161 | * This method checks the x, y, z, widthX, widthY, and height properties of the Cubes.
162 | * @method Cube#intersects
163 | * @param {Cube} b - The second Cube object.
164 | * @return {boolean} A value of true if the specified object intersects with this Cube object; otherwise false.
165 | */
166 | intersects(b) {
167 | return Cube.intersects(this, b);
168 | }
169 |
170 | /**
171 | * Updates and returns an Array of eight Point3 objects containing the corners of this Cube.
172 | * @method Cube#getCorners
173 | * @return {Array.} The corners of this Cube expressed as an Array of eight Point3 objects.
174 | */
175 | getCorners() {
176 | this._corners[0].setTo(this.x, this.y, this.z);
177 | this._corners[1].setTo(this.x, this.y, this.z + this.height);
178 | this._corners[2].setTo(this.x, this.y + this.widthY, this.z);
179 | this._corners[3].setTo(this.x, this.y + this.widthY, this.z + this.height);
180 | this._corners[4].setTo(this.x + this.widthX, this.y, this.z);
181 | this._corners[5].setTo(this.x + this.widthX, this.y, this.z + this.height);
182 | this._corners[6].setTo(this.x + this.widthX, this.y + this.widthY, this.z);
183 | this._corners[7].setTo(this.x + this.widthX, this.y + this.widthY, this.z + this.height);
184 |
185 | return this._corners;
186 | }
187 |
188 | /**
189 | * Returns a string representation of this object.
190 | * @method Cube#toString
191 | * @return {string} A string representation of the instance.
192 | */
193 | toString() {
194 | return `[{Cube (x=${this.x} y=${this.y} z=${this.z} widthX=${this.widthX} widthY=${this.widthY} height=${this.height} empty=${this.empty})}]`;
195 | }
196 |
197 | /**
198 | * @name Cube#halfWidthX
199 | * @property {number} halfWidthX - Half of the widthX of the Cube.
200 | * @readonly
201 | */
202 | get halfWidthX() {
203 | return Math.round(this.widthX * 0.5);
204 | }
205 |
206 | /**
207 | * @name Cube#halfWidthY
208 | * @property {number} halfWidthY - Half of the widthY of the Cube.
209 | * @readonly
210 | */
211 | get halfWidthY() {
212 | return Math.round(this.widthY * 0.5);
213 | }
214 |
215 | /**
216 | * @name Cube#halfHeight
217 | * @property {number} halfHeight - Half of the height of the Cube.
218 | * @readonly
219 | */
220 | get halfHeight() {
221 | return Math.round(this.height * 0.5);
222 | }
223 |
224 | /**
225 | * The z coordinate of the bottom of the Cube. Changing the bottom property of a Cube object has no effect on the x, y, widthX and widthY properties.
226 | * However it does affect the height property, whereas changing the z value does not affect the height property.
227 | * @name Cube#bottom
228 | * @property {number} bottom - The z coordinate of the bottom of the Cube.
229 | */
230 | get bottom() {
231 | return this.z;
232 | }
233 |
234 | set bottom(value) {
235 | if (value >= this.top) {
236 | this.height = 0;
237 | } else {
238 | this.height = (this.top - value);
239 | }
240 | this.z = value;
241 | }
242 |
243 | /**
244 | * The sum of the z and height properties. Changing the top property of a Cube object has no effect on the x, y, z, widthX and widthY properties, but does change the height property.
245 | * @name Cube#top
246 | * @property {number} top - The sum of the z and height properties.
247 | */
248 | get top() {
249 | return this.z + this.height;
250 | }
251 |
252 | set top(value) {
253 | if (value <= this.z) {
254 | this.height = 0;
255 | } else {
256 | this.height = (value - this.z);
257 | }
258 | }
259 |
260 | /**
261 | * The x coordinate of the back of the Cube. Changing the backX property of a Cube object has no effect on the y, z, widthY and height properties. However it does affect the widthX property, whereas changing the x value does not affect the width property.
262 | * @name Cube#backX
263 | * @property {number} backX - The x coordinate of the left of the Cube.
264 | */
265 | get backX() {
266 | return this.x;
267 | }
268 |
269 | set backX(value) {
270 | if (value >= this.frontX) {
271 | this.widthX = 0;
272 | } else {
273 | this.widthX = (this.frontX - value);
274 | }
275 | this.x = value;
276 | }
277 |
278 | /**
279 | * The y coordinate of the back of the Cube. Changing the backY property of a Cube object has no effect on the x, z, widthX and height properties. However it does affect the widthY property, whereas changing the y value does not affect the width property.
280 | * @name Cube#backY
281 | * @property {number} backY - The x coordinate of the left of the Cube.
282 | */
283 | get backY() {
284 | return this.y;
285 | }
286 |
287 | set backY(value) {
288 | if (value >= this.frontY) {
289 | this.widthY = 0;
290 | } else {
291 | this.widthY = (this.frontY - value);
292 | }
293 | this.y = value;
294 | }
295 |
296 | /**
297 | * The sum of the x and widthX properties. Changing the frontX property of a Cube object has no effect on the x, y, z, widthY and height properties, however it does affect the widthX property.
298 | * @name Cube#frontX
299 | * @property {number} frontX - The sum of the x and widthX properties.
300 | */
301 | get frontX() {
302 | return this.x + this.widthX;
303 | }
304 |
305 | set frontX(value) {
306 | if (value <= this.x) {
307 | this.widthX = 0;
308 | } else {
309 | this.widthX = (value - this.x);
310 | }
311 | }
312 |
313 | /**
314 | * The sum of the y and widthY properties. Changing the frontY property of a Cube object has no effect on the x, y, z, widthX and height properties, however it does affect the widthY property.
315 | * @name Cube#frontY
316 | * @property {number} frontY - The sum of the y and widthY properties.
317 | */
318 | get frontY() {
319 | return this.y + this.widthY;
320 | }
321 |
322 | set frontY(value) {
323 | if (value <= this.y) {
324 | this.widthY = 0;
325 | } else {
326 | this.widthY = (value - this.y);
327 | }
328 | }
329 |
330 | /**
331 | * The volume of the Cube derived from widthX * widthY * height.
332 | * @name Cube#volume
333 | * @property {number} volume - The volume of the Cube derived from widthX * widthY * height.
334 | * @readonly
335 | */
336 | get volume() {
337 | return this.widthX * this.widthY * this.height;
338 | }
339 |
340 | /**
341 | * The x coordinate of the center of the Cube.
342 | * @name Cube#centerX
343 | * @property {number} centerX - The x coordinate of the center of the Cube.
344 | */
345 | get centerX() {
346 | return this.x + this.halfWidthX;
347 | }
348 |
349 | set centerX(value) {
350 | this.x = value - this.halfWidthX;
351 | }
352 |
353 | /**
354 | * The y coordinate of the center of the Cube.
355 | * @name Cube#centerY
356 | * @property {number} centerY - The y coordinate of the center of the Cube.
357 | */
358 | get centerY() {
359 | return this.y + this.halfWidthY;
360 | }
361 |
362 | set centerY(value) {
363 | this.y = value - this.halfWidthY;
364 | }
365 |
366 | /**
367 | * The z coordinate of the center of the Cube.
368 | * @name Cube#centerZ
369 | * @property {number} centerZ - The z coordinate of the center of the Cube.
370 | */
371 | get centerZ() {
372 | return this.z + this.halfHeight;
373 | }
374 |
375 | set centerZ(value) {
376 | this.z = value - this.halfHeight;
377 | }
378 |
379 | /**
380 | * A random value between the frontX and backX values (inclusive) of the Cube.
381 | *
382 | * @name Cube#randomX
383 | * @property {number} randomX - A random value between the frontX and backX values (inclusive) of the Cube.
384 | */
385 | get randomX() {
386 | return this.x + (Math.random() * this.widthX);
387 | }
388 |
389 | /**
390 | * A random value between the frontY and backY values (inclusive) of the Cube.
391 | *
392 | * @name Cube#randomY
393 | * @property {number} randomY - A random value between the frontY and backY values (inclusive) of the Cube.
394 | */
395 | get randomY() {
396 | return this.y + (Math.random() * this.widthY);
397 | }
398 |
399 | /**
400 | * A random value between the bottom and top values (inclusive) of the Cube.
401 | *
402 | * @name Cube#randomZ
403 | * @property {number} randomZ - A random value between the bottom and top values (inclusive) of the Cube.
404 | */
405 | get randomZ() {
406 | return this.z + (Math.random() * this.height);
407 | }
408 |
409 | /**
410 | * Determines whether or not this Cube object is empty. A Cube object is empty if its widthX, widthY or height is less than or equal to 0.
411 | * If set to true then all of the Cube properties are set to 0.
412 | * @name Cube#empty
413 | * @property {boolean} empty - Gets or sets the Cube's empty state.
414 | */
415 | get empty() {
416 | return (!this.widthX || !this.widthY || !this.height);
417 | }
418 |
419 | set empty(value) {
420 | if (value === true) {
421 | this.setTo(0, 0, 0, 0, 0, 0);
422 | }
423 | }
424 |
425 | /**
426 | * The size of the Cube object, expressed as a Point3 object with the values of the widthX, widthY and height properties.
427 | * @method Cube.size
428 | * @param {Cube} a - The Cube object.
429 | * @param {Point3} [output] - Optional Point3 object. If given the values will be set into the object, otherwise a brand new Point3 object will be created and returned.
430 | * @return {Point3} The size of the Cube object
431 | */
432 | static size(a, output) {
433 | if (typeof output === 'undefined' || output === null) {
434 | output = new Point3(a.widthX, a.widthY, a.height);
435 | } else {
436 | output.setTo(a.widthX, a.widthY, a.height);
437 | }
438 |
439 | return output;
440 | }
441 |
442 | /**
443 | * Returns a new Cube object with the same values for the x, y, z, widthX, widthY, and height properties as the original Cube object.
444 | * @method Cube.clone
445 | * @param {Cube} a - The Cube object.
446 | * @param {Cube} [output] - Optional Cube object. If given the values will be set into the object, otherwise a brand new Cube object will be created and returned.
447 | * @return {Cube}
448 | */
449 | static clone(a, output) {
450 | if (typeof output === 'undefined' || output === null) {
451 | output = new Cube(a.x, a.y, a.z, a.widthX, a.widthY, a.height);
452 | } else {
453 | output.setTo(a.x, a.y, a.z, a.widthX, a.widthY, a.height);
454 | }
455 |
456 | return output;
457 | }
458 |
459 | /**
460 | * Determines whether the specified coordinates are contained within the region defined by this Cube object.
461 | * @method Cube.contains
462 | * @param {Cube} a - The Cube object.
463 | * @param {number} x - The x coordinate of the point to test.
464 | * @param {number} y - The y coordinate of the point to test.
465 | * @param {number} z - The z coordinate of the point to test.
466 | * @return {boolean} A value of true if the Cube object contains the specified point; otherwise false.
467 | */
468 | static contains(a, x, y, z) {
469 | if (a.widthX <= 0 || a.widthY <= 0 || a.height <= 0) {
470 | return false;
471 | }
472 |
473 | return (x >= a.x && x <= a.frontX && y >= a.y && y <= a.frontY && z >= a.z && z <= a.top);
474 | }
475 |
476 | /**
477 | * Determines whether the specified X and Y coordinates are contained within the region defined by this Cube object.
478 | * @method Cube.containsXY
479 | * @param {Cube} a - The Cube object.
480 | * @param {number} x - The x coordinate of the point to test.
481 | * @param {number} y - The y coordinate of the point to test.
482 | * @return {boolean} A value of true if the Cube object contains the specified point; otherwise false.
483 | */
484 | static containsXY(a, x, y) {
485 | if (a.widthX <= 0 || a.widthY <= 0) {
486 | return false;
487 | }
488 |
489 | return (x >= a.x && x <= a.frontX && y >= a.y && y <= a.frontY);
490 | }
491 |
492 | /**
493 | * Determines whether the specified point is contained within the cubic region defined by this Cube object. This method is similar to the Cube.contains() method, except that it takes a Point3 object as a parameter.
494 | * @method Cube.containsPoint3
495 | * @param {Cube} a - The Cube object.
496 | * @param {Point3} point3 - The Point3 object being checked. Can be Point3 or any object with .x, .y and .z values.
497 | * @return {boolean} A value of true if the Cube object contains the specified point; otherwise false.
498 | */
499 | static containsPoint3(a, point3) {
500 | return Cube.contains(a, point3.x, point3.y, point3.z);
501 | }
502 |
503 | /**
504 | * Determines whether the first Cube object is fully contained within the second Cube object.
505 | * A Cube object is said to contain another if the second Cube object falls entirely within the boundaries of the first.
506 | * @method Cube.containsCube
507 | * @param {Cube} a - The first Cube object.
508 | * @param {Cube} b - The second Cube object.
509 | * @return {boolean} A value of true if the Cube object contains the specified point; otherwise false.
510 | */
511 | static containsCube(a, b) {
512 | // If the given cube has a larger volume than this one then it can never contain it
513 | if (a.volume > b.volume) { return false; }
514 |
515 | return (a.x >= b.x && a.y >= b.y && a.z >= b.z && a.frontX <= b.frontX && a.frontY <= b.frontY && a.top <= b.top);
516 | }
517 |
518 | /**
519 | * Determines whether the two Cubes intersect with each other.
520 | * This method checks the x, y, z, widthX, widthY, and height properties of the Cubes.
521 | * @method Cube.intersects
522 | * @param {Cube} a - The first Cube object.
523 | * @param {Cube} b - The second Cube object.
524 | * @return {boolean} A value of true if the specified object intersects with this Cube object; otherwise false.
525 | */
526 | static intersects(a, b) {
527 | if (a.widthX <= 0 || a.widthY <= 0 || a.height <= 0 || b.widthX <= 0 || b.widthY <= 0 || b.height <= 0) {
528 | return false;
529 | }
530 | return !(a.frontX < b.x || a.frontY < b.y || a.x > b.frontX || a.y > b.frontY || a.z > b.top || a.top < b.z);
531 | }
532 | }
533 |
534 | export default Cube;
535 |
--------------------------------------------------------------------------------
/src/physics/Body.js:
--------------------------------------------------------------------------------
1 | import Point3 from '../Point3';
2 | import Cube from '../Cube';
3 |
4 | const UP = 0;
5 | const DOWN = 1;
6 | const FORWARDX = 2;
7 | const FORWARDY = 3;
8 | const BACKWARDX = 4;
9 | const BACKWARDY = 5;
10 | export const TYPE = 'IsoPhysics';
11 |
12 | /**
13 | * @class Isometric.Body
14 | *
15 | * @classdesc
16 | * The Physics Body is linked to a single IsoSprite. All physics operations should be performed against the body rather than
17 | * the IsoSprite itself. For example you can set the velocity, acceleration, bounce values etc all on the Body.
18 | */
19 | export default class Body {
20 | /*
21 | * @constructor
22 | * @param {IsoSprite} sprite - The IsoSprite object this physics body belongs to.
23 | */
24 | constructor(sprite) {
25 | /**
26 | * @property {IsoSprite} sprite - Reference to the parent IsoSprite.
27 | */
28 | this.sprite = sprite;
29 |
30 | /**
31 | * @property {Phaser.Scene} scene - Local reference to scene.
32 | */
33 | this.scene = sprite.scene;
34 |
35 | /**
36 | * @property {number} type - The type of physics system this body belongs to.
37 | */
38 | this.type = TYPE;
39 |
40 | /**
41 | * @property {boolean} enable - A disabled body won't be checked for any form of collision or overlap or have its pre/post updates run.
42 | * @default
43 | */
44 | this.enable = true;
45 |
46 | /**
47 | * @property {Phaser.Point} offset - The offset of the Physics Body from the IsoSprite x/y/z position.
48 | */
49 | this.offset = new Point3();
50 |
51 | /**
52 | * @property {Point3} position - The position of the physics body.
53 | * @readonly
54 | */
55 | this.position = new Point3(sprite.isoX, sprite.isoY, sprite.isoZ);
56 |
57 | /**
58 | * @property {Phaser.Point} prev - The previous position of the physics body.
59 | * @readonly
60 | */
61 | this.prev = new Point3(this.position.x, this.position.y, this.position.z);
62 |
63 | /**
64 | * @property {boolean} allowRotation - Allow this Body to be rotated? (via angularVelocity, etc)
65 | * @default
66 | */
67 | this.allowRotation = true;
68 |
69 | /**
70 | * @property {number} rotation - The amount the Body is rotated.
71 | */
72 | this.rotation = sprite.rotation;
73 |
74 | /**
75 | * @property {number} preRotation - The previous rotation of the physics body.
76 | * @readonly
77 | */
78 | this.preRotation = sprite.rotation;
79 |
80 | /**
81 | * @property {number} sourceWidthX - The un-scaled original size.
82 | * @readonly
83 | */
84 | this.sourceWidthX = sprite.width / sprite.scaleX;
85 |
86 | /**
87 | * @property {number} sourceWidthY - The un-scaled original size.
88 | * @readonly
89 | */
90 | this.sourceWidthY = sprite.width / sprite.scaleX;
91 |
92 | /**
93 | * @property {number} sourceHeight - The un-scaled original size.
94 | * @readonly
95 | */
96 | this.sourceHeight = sprite.height / sprite.scaleY;
97 |
98 | /**
99 | * @property {number} widthX - The calculated X width (breadth) of the physics body.
100 | */
101 | this.widthX = Math.ceil(sprite.width * 0.5);
102 |
103 | /**
104 | * @property {number} widthY - The calculated Y width (depth) of the physics body.
105 | */
106 | this.widthY = Math.ceil(sprite.width * 0.5);
107 |
108 | /**
109 | * @property {number} height - The calculated height of the physics body.
110 | */
111 | this.height = sprite.height - Math.ceil(sprite.width * 0.5);
112 |
113 | /**
114 | * @property {number} halfWidthX - The calculated X width / 2 of the physics body.
115 | */
116 | this.halfWidthX = Math.abs(this.widthX * 0.5);
117 |
118 | /**
119 | * @property {number} halfWidthX - The calculated X width / 2 of the physics body.
120 | */
121 | this.halfWidthY = Math.abs(this.widthY * 0.5);
122 |
123 | /**
124 | * @property {number} halfHeight - The calculated height / 2 of the physics body.
125 | */
126 | this.halfHeight = Math.abs(this.height * 0.5);
127 |
128 | /**
129 | * @property {Point3} center - The center coordinate of the physics body.
130 | */
131 | this.center = new Point3(sprite.isoX + this.halfWidthX, sprite.isoY + this.halfWidthY, sprite.isoZ + this.halfHeight);
132 |
133 | /**
134 | * @property {Point3} velocity - The velocity in pixels per second sq. of the Body.
135 | */
136 | this.velocity = new Point3();
137 |
138 | /**
139 | * @property {Point3} newVelocity - New velocity.
140 | * @readonly
141 | */
142 | this.newVelocity = new Point3();
143 |
144 | /**
145 | * @property {Point3} deltaMax - The Sprite position is updated based on the delta x/y values. You can set a cap on those (both +-) using deltaMax.
146 | */
147 | this.deltaMax = new Point3();
148 |
149 | /**
150 | * @property {Point3} acceleration - The velocity in pixels per second sq. of the Body.
151 | */
152 | this.acceleration = new Point3();
153 |
154 | /**
155 | * @property {Point3} drag - The drag applied to the motion of the Body.
156 | */
157 | this.drag = new Point3();
158 |
159 | /**
160 | * @property {boolean} allowGravity - Allow this Body to be influenced by gravity? Either world or local.
161 | * @default
162 | */
163 | this.allowGravity = true;
164 |
165 | /**
166 | * @property {Point3} gravity - A local gravity applied to this Body. If non-zero this over rides any world gravity, unless Body.allowGravity is set to false.
167 | */
168 | this.gravity = new Point3();
169 |
170 | /**
171 | * @property {Point3} bounce - The elasticitiy of the Body when colliding. bounce.x/y/z = 1 means full rebound, bounce.x/y/z = 0.5 means 50% rebound velocity.
172 | */
173 | this.bounce = new Point3();
174 |
175 | /**
176 | * @property {Point3} maxVelocity - The maximum velocity in pixels per second sq. that the Body can reach.
177 | * @default
178 | */
179 | this.maxVelocity = new Point3(10000, 10000, 10000);
180 |
181 | /**
182 | * @property {number} angularVelocity - The angular velocity in pixels per second sq. of the Body.
183 | * @default
184 | */
185 | this.angularVelocity = 0;
186 |
187 | /**
188 | * @property {number} angularAcceleration - The angular acceleration in pixels per second sq. of the Body.
189 | * @default
190 | */
191 | this.angularAcceleration = 0;
192 |
193 | /**
194 | * @property {number} angularDrag - The angular drag applied to the rotation of the Body.
195 | * @default
196 | */
197 | this.angularDrag = 0;
198 |
199 | /**
200 | * @property {number} maxAngular - The maximum angular velocity in pixels per second sq. that the Body can reach.
201 | * @default
202 | */
203 | this.maxAngular = 1000;
204 |
205 | /**
206 | * @property {number} mass - The mass of the Body.
207 | * @default
208 | */
209 | this.mass = 1;
210 |
211 | /**
212 | * @property {number} angle - The angle of the Body in radians as calculated by its velocity, rather than its visual angle.
213 | * @readonly
214 | */
215 | this.angle = 0;
216 |
217 | /**
218 | * @property {number} speed - The speed of the Body as calculated by its velocity.
219 | * @readonly
220 | */
221 | this.speed = 0;
222 |
223 | /**
224 | * @property {number} facing - A const reference to the direction the Body is traveling or facing.
225 | * @default
226 | */
227 | this.facing = Phaser.NONE;
228 |
229 | /**
230 | * @property {boolean} immovable - An immovable Body will not receive any impacts from other bodies.
231 | * @default
232 | */
233 | this.immovable = false;
234 |
235 | /**
236 | * If you have a Body that is being moved around the world via a tween or a Group motion, but its local x/y position never
237 | * actually changes, then you should set Body.moves = false. Otherwise it will most likely fly off the screen.
238 | * If you want the physics system to move the body around, then set moves to true.
239 | * @property {boolean} moves - Set to true to allow the Physics system to move this Body, other false to move it manually.
240 | * @default
241 | */
242 | this.moves = true;
243 |
244 | /**
245 | * This flag allows you to disable the custom x separation that takes place by Physics.IsoArcade.separate.
246 | * Used in combination with your own collision processHandler you can create whatever type of collision response you need.
247 | * @property {boolean} customSeparateX - Use a custom separation system or the built-in one?
248 | * @default
249 | */
250 | this.customSeparateX = false;
251 |
252 | /**
253 | * This flag allows you to disable the custom y separation that takes place by Physics.IsoArcade.separate.
254 | * Used in combination with your own collision processHandler you can create whatever type of collision response you need.
255 | * @property {boolean} customSeparateY - Use a custom separation system or the built-in one?
256 | * @default
257 | */
258 | this.customSeparateY = false;
259 |
260 | /**
261 | * This flag allows you to disable the custom z separation that takes place by Physics.IsoArcade.separate.
262 | * Used in combination with your own collision processHandler you can create whatever type of collision response you need.
263 | * @property {boolean} customSeparateZ - Use a custom separation system or the built-in one?
264 | * @default
265 | */
266 | this.customSeparateZ = false;
267 |
268 | /**
269 | * When this body collides with another, the amount of overlap is stored here.
270 | * @property {number} overlapX - The amount of horizontal overlap during the collision.
271 | */
272 | this.overlapX = 0;
273 |
274 | /**
275 | * When this body collides with another, the amount of overlap is stored here.
276 | * @property {number} overlapY - The amount of vertical overlap during the collision.
277 | */
278 | this.overlapY = 0;
279 |
280 | /**
281 | * When this body collides with another, the amount of overlap is stored here.
282 | * @property {number} overlapY - The amount of vertical overlap during the collision.
283 | */
284 | this.overlapZ = 0;
285 |
286 | /**
287 | * If a body is overlapping with another body, but neither of them are moving (maybe they spawned on-top of each other?) this is set to true.
288 | * @property {boolean} embedded - Body embed value.
289 | */
290 | this.embedded = false;
291 |
292 | /**
293 | * A Body can be set to collide against the World bounds automatically and rebound back into the World if this is set to true. Otherwise it will leave the World.
294 | * @property {boolean} collideWorldBounds - Should the Body collide with the World bounds?
295 | */
296 | this.collideWorldBounds = false;
297 |
298 | /**
299 | * Set the checkCollision properties to control which directions collision is processed for this Body.
300 | * For example checkCollision.up = false means it won't collide when the collision happened while moving up.
301 | * @property {object} checkCollision - An object containing allowed collision.
302 | */
303 | this.checkCollision = {
304 | none: false,
305 | any: true,
306 | up: true,
307 | down: true,
308 | frontX: true,
309 | frontY: true,
310 | backX: true,
311 | backY: true
312 | };
313 |
314 | /**
315 | * This object is populated with boolean values when the Body collides with another.
316 | * touching.up = true means the collision happened to the top of this Body for example.
317 | * @property {object} touching - An object containing touching results.
318 | */
319 | this.touching = {
320 | none: true,
321 | up: false,
322 | down: false,
323 | frontX: false,
324 | frontY: false,
325 | backX: false,
326 | backY: false
327 | };
328 |
329 | /**
330 | * This object is populated with previous touching values from the bodies previous collision.
331 | * @property {object} wasTouching - An object containing previous touching results.
332 | */
333 | this.wasTouching = {
334 | none: true,
335 | up: false,
336 | down: false,
337 | frontX: false,
338 | frontY: false,
339 | backX: false,
340 | backY: false
341 | };
342 |
343 | /**
344 | * This object is populated with boolean values when the Body collides with the World bounds or a Tile.
345 | * For example if blocked.up is true then the Body cannot move up.
346 | * @property {object} blocked - An object containing on which faces this Body is blocked from moving, if any.
347 | */
348 | this.blocked = {
349 | up: false,
350 | down: false,
351 | frontX: false,
352 | frontY: false,
353 | backX: false,
354 | backY: false
355 | };
356 |
357 | /**
358 | * @property {number} phase - Is this Body in a update (1) or postUpdate (2) state?
359 | */
360 | this.phase = 0;
361 |
362 | /**
363 | * @property {boolean} skipTree - If true and you collide this IsoSprite against a Group, it will disable the collision check from using an Octree.
364 | */
365 | this.skipTree = false;
366 |
367 | /**
368 | * @property {boolean} _reset - Internal cache var.
369 | * @private
370 | */
371 | this._reset = true;
372 |
373 | /**
374 | * @property {number} _sx - Internal cache var.
375 | * @private
376 | */
377 | this._sx = sprite.scaleX;
378 |
379 | /**
380 | * @property {number} _sy - Internal cache var.
381 | * @private
382 | */
383 | this._sy = sprite.scaleY;
384 |
385 | /**
386 | * @property {number} _dx - Internal cache var.
387 | * @private
388 | */
389 | this._dx = 0;
390 |
391 | /**
392 | * @property {number} _dy - Internal cache var.
393 | * @private
394 | */
395 | this._dy = 0;
396 |
397 | /**
398 | * @property {number} _dz - Internal cache var.
399 | * @private
400 | */
401 | this._dz = 0;
402 |
403 | /**
404 | * @property {Array.} _corners - The 8 corners of the bounding cube.
405 | * @private
406 | */
407 | this._corners = [new Point3(this.x, this.y, this.z),
408 | new Point3(this.x, this.y, this.z + this.height),
409 | new Point3(this.x, this.y + this.widthY, this.z),
410 | new Point3(this.x, this.y + this.widthY, this.z + this.height),
411 | new Point3(this.x + this.widthX, this.y, this.z),
412 | new Point3(this.x + this.widthX, this.y, this.z + this.height),
413 | new Point3(this.x + this.widthX, this.y + this.widthY, this.z),
414 | new Point3(this.x + this.widthX, this.y + this.widthY, this.z + this.height)
415 | ];
416 | }
417 |
418 | /**
419 | * Internal method.
420 | *
421 | * @method Body#updateBounds
422 | * @protected
423 | */
424 | updateBounds() {
425 | var asx = Math.abs(this.sprite.scaleX);
426 | var asy = Math.abs(this.sprite.scaleY);
427 |
428 | if (asx !== this._sx || asy !== this._sy) {
429 | this.widthX = Math.ceil(this.sprite.width * 0.5);
430 | this.widthY = Math.ceil(this.sprite.width * 0.5);
431 | this.height = Math.ceil(this.sprite.height - (this.sprite.width * 0.5));
432 | this.halfWidthX = Math.floor(this.widthX * 0.5);
433 | this.halfWidthY = Math.floor(this.widthY * 0.5);
434 | this.halfHeight = Math.floor(this.height * 0.5);
435 | this._sx = asx;
436 | this._sy = asy;
437 | this.center.setTo(this.position.x + this.halfWidthX, this.position.y + this.halfWidthY, this.position.z + this.halfHeight);
438 |
439 | this._reset = true;
440 | }
441 | }
442 |
443 | /**
444 | * Internal method.
445 | *
446 | * @method Body#update
447 | * @protected
448 | */
449 | update(time, delta) {
450 | if (!this.enable) { return; }
451 |
452 | this.phase = 1;
453 |
454 | // Store and reset collision flags
455 | this.wasTouching.none = this.touching.none;
456 | this.wasTouching.up = this.touching.up;
457 | this.wasTouching.down = this.touching.down;
458 | this.wasTouching.backX = this.touching.backX;
459 | this.wasTouching.backY = this.touching.backY;
460 | this.wasTouching.frontX = this.touching.frontX;
461 | this.wasTouching.frontY = this.touching.frontY;
462 |
463 | this.touching.none = true;
464 | this.touching.up = false;
465 | this.touching.down = false;
466 | this.touching.backX = false;
467 | this.touching.backY = false;
468 | this.touching.frontX = false;
469 | this.touching.frontY = false;
470 |
471 | this.blocked.up = false;
472 | this.blocked.down = false;
473 | this.blocked.frontY = false;
474 | this.blocked.frontX = false;
475 | this.blocked.backY = false;
476 | this.blocked.backX = false;
477 |
478 | this.embedded = false;
479 |
480 | this.updateBounds();
481 |
482 | // Working out how to incorporate anchors into this was... fun.
483 | this.position.x = this.sprite.isoX + ((this.widthX * -this.sprite.originX) + this.widthX * 0.5) + this.offset.x;
484 | this.position.y = this.sprite.isoY + ((this.widthY * this.sprite.originX) - this.widthY * 0.5) + this.offset.y;
485 | this.position.z = this.sprite.isoZ - (Math.abs(this.sprite.height) * (1 - this.sprite.originY)) + (Math.abs(this.sprite.width * 0.5)) + this.offset.z;
486 |
487 |
488 | this.rotation = this.sprite.angle;
489 |
490 | this.preRotation = this.rotation;
491 |
492 | if (this._reset || this.sprite.fresh === true) {
493 | this.prev.x = this.position.x;
494 | this.prev.y = this.position.y;
495 | this.prev.z = this.position.z;
496 | }
497 |
498 | if (this.moves) {
499 | const pluginKey = this.scene.sys.settings.map.isoPhysics;
500 | const { world } = this.scene[pluginKey];
501 | delta /= 1000;
502 | world.updateMotion(this, delta);
503 |
504 | this.newVelocity.set(this.velocity.x * delta, this.velocity.y * delta, this.velocity.z * delta);
505 |
506 | this.position.x += this.newVelocity.x;
507 | this.position.y += this.newVelocity.y;
508 | this.position.z += this.newVelocity.z;
509 |
510 | if (this.position.x !== this.prev.x || this.position.y !== this.prev.y || this.position.z !== this.prev.z) {
511 | this.speed = Math.sqrt(this.velocity.x * this.velocity.x + this.velocity.y * this.velocity.y + this.velocity.z * this.velocity.z);
512 | this.angle = Math.atan2(this.velocity.y, this.velocity.x);
513 | }
514 |
515 | // Now the State update will throw collision checks at the Body
516 | // And finally we'll integrate the new position back to the Sprite in postUpdate
517 |
518 | if (this.collideWorldBounds) {
519 | this.checkWorldBounds();
520 | }
521 |
522 | if (this.sprite.outOfBoundsKill && !world.bounds.intersects(this.sprite.isoBounds)){
523 | this.sprite.kill();
524 | }
525 | }
526 |
527 | this._dx = this.deltaX();
528 | this._dy = this.deltaY();
529 | this._dz = this.deltaZ();
530 |
531 | this._reset = false;
532 | }
533 |
534 | /**
535 | * Internal method.
536 | *
537 | * @method Body#postUpdate
538 | * @protected
539 | */
540 | postUpdate() {
541 | // Only allow postUpdate to be called once per frame
542 | if (!this.enable || this.phase === 2) { return; }
543 |
544 | this.phase = 2;
545 |
546 | // stops sprites flying off if isoPosition is changed during update
547 | if (this._reset) {
548 | this.prev.x = this.position.x;
549 | this.prev.y = this.position.y;
550 | this.prev.z = this.position.z;
551 | }
552 |
553 | if (this.deltaAbsX() >= this.deltaAbsY() && this.deltaAbsX() >= this.deltaAbsZ()){
554 | if (this.deltaX() < 0) {
555 | this.facing = BACKWARDX;
556 | } else if (this.deltaX() > 0) {
557 | this.facing = FORWARDX;
558 | }
559 | } else if (this.deltaAbsY() >= this.deltaAbsX() && this.deltaAbsY() >= this.deltaAbsZ()){
560 | if (this.deltaY() < 0) {
561 | this.facing = BACKWARDY;
562 | } else if (this.deltaY() > 0) {
563 | this.facing = FORWARDY;
564 | }
565 | } else {
566 | if (this.deltaZ() < 0) {
567 | this.facing = DOWN;
568 | } else if (this.deltaZ() > 0) {
569 | this.facing = UP;
570 | }
571 | }
572 |
573 | if (this.moves) {
574 | this._dx = this.deltaX();
575 | this._dy = this.deltaY();
576 | this._dz = this.deltaZ();
577 |
578 | if (this.deltaMax.x !== 0 && this._dx !== 0) {
579 | if (this._dx < 0 && this._dx < -this.deltaMax.x) {
580 | this._dx = -this.deltaMax.x;
581 | } else if (this._dx > 0 && this._dx > this.deltaMax.x) {
582 | this._dx = this.deltaMax.x;
583 | }
584 | }
585 |
586 | if (this.deltaMax.y !== 0 && this._dy !== 0) {
587 | if (this._dy < 0 && this._dy < -this.deltaMax.y) {
588 | this._dy = -this.deltaMax.y;
589 | } else if (this._dy > 0 && this._dy > this.deltaMax.y) {
590 | this._dy = this.deltaMax.y;
591 | }
592 | }
593 |
594 | if (this.deltaMax.z !== 0 && this._dz !== 0) {
595 | if (this._dz < 0 && this._dz < -this.deltaMax.z) {
596 | this._dz = -this.deltaMax.z;
597 | } else if (this._dz > 0 && this._dz > this.deltaMax.z) {
598 | this._dz = this.deltaMax.z;
599 | }
600 | }
601 |
602 | this.sprite.isoX += this._dx;
603 | this.sprite.isoY += this._dy;
604 | this.sprite.isoZ += this._dz;
605 | }
606 |
607 | this.center.setTo(this.position.x + this.halfWidthX, this.position.y + this.halfWidthY, this.position.z + this.halfHeight);
608 |
609 | if (this.allowRotation) {
610 | this.sprite.angle += this.deltaR();
611 | }
612 |
613 | this.prev.x = this.position.x;
614 | this.prev.y = this.position.y;
615 | this.prev.z = this.position.z;
616 |
617 | this._reset = false;
618 | }
619 |
620 | /**
621 | * Removes this body's reference to its parent sprite, freeing it up for gc.
622 | *
623 | * @method Body#destroy
624 | */
625 | destroy() {
626 | this.sprite = null;
627 | }
628 |
629 | /**
630 | * Internal method.
631 | *
632 | * @method Body#checkWorldBounds
633 | * @protected
634 | */
635 | checkWorldBounds() {
636 | const pluginKey = this.scene.sys.settings.map.isoPhysics;
637 | const { world } = this.scene[pluginKey];
638 |
639 | if (this.position.x < world.bounds.x && world.checkCollision.backX) {
640 | this.position.x = world.bounds.x;
641 | this.velocity.x *= -this.bounce.x;
642 | this.blocked.backX = true;
643 | } else if (this.frontX > world.bounds.frontX && world.checkCollision.frontX) {
644 | this.position.x = world.bounds.frontX - this.widthX;
645 | this.velocity.x *= -this.bounce.x;
646 | this.blocked.frontX = true;
647 | }
648 |
649 | if (this.position.y < world.bounds.y && world.checkCollision.backY) {
650 | this.position.y = world.bounds.y;
651 | this.velocity.y *= -this.bounce.y;
652 | this.blocked.backY = true;
653 | } else if (this.frontY > world.bounds.frontY && world.checkCollision.frontY) {
654 | this.position.y = world.bounds.frontY - this.widthY;
655 | this.velocity.y *= -this.bounce.y;
656 | this.blocked.frontY = true;
657 | }
658 |
659 | if (this.position.z < world.bounds.z && world.checkCollision.down) {
660 | this.position.z = world.bounds.z;
661 | this.velocity.z *= -this.bounce.z;
662 | this.blocked.down = true;
663 | } else if (this.top > world.bounds.top && world.checkCollision.up) {
664 | this.position.z = world.bounds.top - this.height;
665 | this.velocity.z *= -this.bounce.z;
666 | this.blocked.up = true;
667 | }
668 | }
669 |
670 | /**
671 | * You can modify the size of the physics Body to be any dimension you need.
672 | * So it could be smaller or larger than the parent Sprite. You can also control the x, y and z offset, which
673 | * is the position of the Body relative to the center of the Sprite.
674 | *
675 | * @method Body#setSize
676 | * @param {number} widthX - The X width (breadth) of the Body.
677 | * @param {number} widthY - The Y width (depth) of the Body.
678 | * @param {number} height - The height of the Body.
679 | * @param {number} [offsetX] - The X offset of the Body from the Sprite position.
680 | * @param {number} [offsetY] - The Y offset of the Body from the Sprite position.
681 | * @param {number} [offsetZ] - The Z offset of the Body from the Sprite position.
682 | */
683 | setSize(widthX, widthY, height, offsetX, offsetY, offsetZ) {
684 | if (typeof offsetX === 'undefined') {
685 | offsetX = this.offset.x;
686 | }
687 | if (typeof offsetY === 'undefined') {
688 | offsetY = this.offset.y;
689 | }
690 | if (typeof offsetZ === 'undefined') {
691 | offsetZ = this.offset.z;
692 | }
693 |
694 | this.sourceWidthX = widthX;
695 | this.sourceWidthY = widthY;
696 | this.sourceHeight = height;
697 | this.widthX = (this.sourceWidthX) * this._sx;
698 | this.widthY = (this.sourceWidthY) * this._sx;
699 | this.height = (this.sourceHeight) * this._sy;
700 | this.halfWidthX = Math.floor(this.widthX * 0.5);
701 | this.halfWidthY = Math.floor(this.widthY * 0.5);
702 | this.halfHeight = Math.floor(this.height * 0.5);
703 | this.offset.setTo(offsetX, offsetY, offsetZ);
704 |
705 | this.center.setTo(this.position.x + this.halfWidthX, this.position.y + this.halfWidthY, this.position.z + this.halfHeight);
706 | }
707 |
708 | /**
709 | * Resets all Body values (velocity, acceleration, rotation, etc)
710 | *
711 | * @method Body#reset
712 | * @param {number} x - The new x position of the Body.
713 | * @param {number} y - The new y position of the Body.
714 | * @param {number} z - The new z position of the Body.
715 | */
716 | reset(x, y, z) {
717 | this.velocity.set(0);
718 | this.acceleration.set(0);
719 |
720 | this.angularVelocity = 0;
721 | this.angularAcceleration = 0;
722 |
723 | this.position.x = x + ((this.widthX * -this.sprite.anchor.x) + this.widthX * 0.5) + this.offset.x;
724 | this.position.y = y + ((this.widthY * this.sprite.anchor.x) - this.widthY * 0.5) + this.offset.y;
725 | this.position.z = z - (Math.abs(this.sprite.height) * (1 - this.sprite.anchor.y)) + (Math.abs(this.sprite.width * 0.5)) + this.offset.z;
726 |
727 | this.prev.x = this.position.x;
728 | this.prev.y = this.position.y;
729 | this.prev.z = this.position.z;
730 |
731 | this.rotation = this.sprite.angle;
732 | this.preRotation = this.rotation;
733 |
734 | this._sx = this.sprite.scaleX;
735 | this._sy = this.sprite.scaleY;
736 |
737 | this.center.setTo(this.position.x + this.halfWidthX, this.position.y + this.halfWidthY, this.position.z + this.halfHeight);
738 |
739 | this.sprite._isoPositionChanged = true;
740 | }
741 |
742 | /**
743 | * Tests if a world point lies within this Body.
744 | *
745 | * @method Body#hitTest
746 | * @param {number} x - The world x coordinate to test.
747 | * @param {number} y - The world y coordinate to test.
748 | * @param {number} z - The world z coordinate to test.
749 | * @return {boolean} True if the given coordinates are inside this Body, otherwise false.
750 | */
751 | hitTest(x, y, z) {
752 | return Cube.contains(this, x, y, z);
753 | }
754 |
755 | /**
756 | * Returns true if the bottom of this Body is in contact with either the world bounds.
757 | *
758 | * @method Body#onFloor
759 | * @return {boolean} True if in contact with either the world bounds.
760 | */
761 | onFloor() {
762 | return this.blocked.down;
763 | }
764 |
765 | /**
766 | * Returns true if either side of this Body is in contact with either the world bounds.
767 | *
768 | * @method Body#onWall
769 | * @return {boolean} True if in contact with world bounds.
770 | */
771 | onWall() {
772 | return (this.blocked.frontX || this.blocked.frontY || this.blocked.backX || this.blocked.backY);
773 | }
774 |
775 | /**
776 | * Returns the absolute delta x value.
777 | *
778 | * @method Body#deltaAbsX
779 | * @return {number} The absolute delta value.
780 | */
781 | deltaAbsX() {
782 | return (this.deltaX() > 0 ? this.deltaX() : -this.deltaX());
783 | }
784 |
785 | /**
786 | * Returns the absolute delta y value.
787 | *
788 | * @method Body#deltaAbsY
789 | * @return {number} The absolute delta value.
790 | */
791 | deltaAbsY() {
792 | return (this.deltaY() > 0 ? this.deltaY() : -this.deltaY());
793 | }
794 |
795 | /**
796 | * Returns the absolute delta z value.
797 | *
798 | * @method Body#deltaAbsZ
799 | * @return {number} The absolute delta value.
800 | */
801 | deltaAbsZ() {
802 | return (this.deltaZ() > 0 ? this.deltaZ() : -this.deltaZ());
803 | }
804 |
805 | /**
806 | * Returns the delta x value. The difference between Body.x now and in the previous step.
807 | *
808 | * @method Body#deltaX
809 | * @return {number} The delta value. Positive if the motion was to the right, negative if to the left.
810 | */
811 | deltaX() {
812 | return this.position.x - this.prev.x;
813 | }
814 |
815 | /**
816 | * Returns the delta y value. The difference between Body.y now and in the previous step.
817 | *
818 | * @method Body#deltaY
819 | * @return {number} The delta value. Positive if the motion was downwards, negative if upwards.
820 | */
821 | deltaY() {
822 | return this.position.y - this.prev.y;
823 | }
824 |
825 | /**
826 | * Returns the delta z value. The difference between Body.z now and in the previous step.
827 | *
828 | * @method Body#deltaZ
829 | * @return {number} The delta value. Positive if the motion was downwards, negative if upwards.
830 | */
831 | deltaZ() {
832 | return this.position.z - this.prev.z;
833 | }
834 |
835 | /**
836 | * Returns the delta r value. The difference between Body.rotation now and in the previous step.
837 | *
838 | * @method Body#deltaR
839 | * @return {number} The delta value. Positive if the motion was clockwise, negative if anti-clockwise.
840 | */
841 | deltaR() {
842 | return this.rotation - this.preRotation;
843 | }
844 |
845 | /**
846 | * Returns the 8 corners that make up the body's bounding cube.
847 | *
848 | * @method Body#getCorners
849 | * @return {Array.} An array of Point3 values specifying each corner co-ordinate.
850 | */
851 | getCorners() {
852 | this._corners[0].setTo(this.x, this.y, this.z);
853 | this._corners[1].setTo(this.x, this.y, this.z + this.height);
854 | this._corners[2].setTo(this.x, this.y + this.widthY, this.z);
855 | this._corners[3].setTo(this.x, this.y + this.widthY, this.z + this.height);
856 | this._corners[4].setTo(this.x + this.widthX, this.y, this.z);
857 | this._corners[5].setTo(this.x + this.widthX, this.y, this.z + this.height);
858 | this._corners[6].setTo(this.x + this.widthX, this.y + this.widthY, this.z);
859 | this._corners[7].setTo(this.x + this.widthX, this.y + this.widthY, this.z + this.height);
860 |
861 | return this._corners;
862 | }
863 |
864 | /**
865 | * @name Body#top
866 | * @property {number} bottom - The top value of this Body (same as Body.z + Body.height)
867 | * @readonly
868 | */
869 | get top() {
870 | return this.position.z + this.height;
871 | }
872 |
873 | /**
874 | * @name Body#frontX
875 | * @property {number} right - The front X value of this Body (same as Body.x + Body.widthX)
876 | * @readonly
877 | */
878 |
879 | get frontX() {
880 | return this.position.x + this.widthX;
881 | }
882 |
883 | /**
884 | * @name Body#right
885 | * @property {number} right - The front X value of this Body (same as Body.x + Body.widthX) - alias used for QuadTree
886 | * @readonly
887 | */
888 |
889 | get right() {
890 | return this.position.x + this.widthX;
891 | }
892 |
893 | /**
894 | * @name Body#frontY
895 | * @property {number} right - The front Y value of this Body (same as Body.y + Body.widthY)
896 | * @readonly
897 | */
898 |
899 | get frontY() {
900 | return this.position.y + this.widthY;
901 | }
902 |
903 | /**
904 | * @name Body#bottom
905 | * @property {number} right - The front Y value of this Body (same as Body.y + Body.widthY) - alias used for QuadTree
906 | * @readonly
907 | */
908 |
909 | get bottom() {
910 | return this.position.y + this.widthY;
911 | }
912 |
913 | /**
914 | * @name Body#x
915 | * @property {number} x - The x position.
916 | */
917 |
918 | get x() {
919 | return this.position.x;
920 | }
921 |
922 | set x(value) {
923 | this.position.x = value;
924 | }
925 |
926 | /**
927 | * @name Body#y
928 | * @property {number} y - The y position.
929 | */
930 |
931 | get y() {
932 | return this.position.y;
933 | }
934 |
935 | set y(value) {
936 | this.position.y = value;
937 | }
938 |
939 | /**
940 | * @name Body#z
941 | * @property {number} z - The z position.
942 | */
943 |
944 | get z() {
945 | return this.position.z;
946 | }
947 |
948 | set z(value) {
949 | this.position.z = value;
950 | }
951 |
952 | debugRender(context, color = 'rgba(0,255,0,0.4)', filled = true) {
953 | var points = [];
954 | var corners = this.getCorners();
955 |
956 | var posX = -this.scene.cameras.main.x;
957 | var posY = -this.scene.cameras.main.y;
958 |
959 | const pluginKey = this.scene.sys.settings.map.isoPlugin;
960 | const projector = this.scene[pluginKey].projector;
961 |
962 | if (filled) {
963 | points = [corners[1], corners[3], corners[2], corners[6], corners[4], corners[5], corners[1]];
964 |
965 | points = points.map(p => {
966 | var newPos = projector.project(p);
967 | newPos.x += posX;
968 | newPos.y += posY;
969 | return newPos;
970 | });
971 |
972 | context.beginPath();
973 | context.fillStyle = color;
974 | context.moveTo(points[0].x, points[0].y);
975 |
976 | for (var i = 1; i < points.length; i++) {
977 | context.lineTo(points[i].x, points[i].y);
978 | }
979 |
980 | context.fill();
981 | } else {
982 | points = corners.slice(0, corners.length);
983 | points = points.map(p => {
984 | var newPos = projector.project(p);
985 | newPos.x += posX;
986 | newPos.y += posY;
987 | return newPos;
988 | });
989 |
990 | context.moveTo(points[0].x, points[0].y);
991 | context.beginPath();
992 | context.strokeStyle = color;
993 |
994 | context.lineTo(points[1].x, points[1].y);
995 | context.lineTo(points[3].x, points[3].y);
996 | context.lineTo(points[2].x, points[2].y);
997 | context.lineTo(points[6].x, points[6].y);
998 | context.lineTo(points[4].x, points[4].y);
999 | context.lineTo(points[5].x, points[5].y);
1000 | context.lineTo(points[1].x, points[1].y);
1001 | context.lineTo(points[0].x, points[0].y);
1002 | context.lineTo(points[4].x, points[4].y);
1003 | context.moveTo(points[0].x, points[0].y);
1004 | context.lineTo(points[2].x, points[2].y);
1005 | context.moveTo(points[3].x, points[3].y);
1006 | context.lineTo(points[7].x, points[7].y);
1007 | context.lineTo(points[6].x, points[6].y);
1008 | context.moveTo(points[7].x, points[7].y);
1009 | context.lineTo(points[5].x, points[5].y);
1010 | context.stroke();
1011 | context.closePath();
1012 | }
1013 | }
1014 | }
1015 |
1016 | /**
1017 | * Render IsoSprite Body Physics Data as text.
1018 | *
1019 | * @method Body#renderBodyInfo
1020 | * @param {Body} body - The Body to render the info of.
1021 | * @param {number} x - X position of the debug info to be rendered.
1022 | * @param {number} y - Y position of the debug info to be rendered.
1023 | * @param {string} [color='rgb(255,255,255)'] - color of the debug info to be rendered. (format is css color string).
1024 | */
1025 | // Body.renderBodyInfo = function (debug, body) {
1026 | //
1027 | // debug.line('x: ' + body.x.toFixed(2), 'y: ' + body.y.toFixed(2), 'z: ' + body.z.toFixed(2), 'widthX: ' + body.widthX, 'widthY: ' + body.widthY, 'height: ' + body.height);
1028 | // debug.line('velocity x: ' + body.velocity.x.toFixed(2), 'y: ' + body.velocity.y.toFixed(2), 'z: ' + body.velocity.z.toFixed(2), 'deltaX: ' + body._dx.toFixed(2), 'deltaY: ' + body._dy.toFixed(2), 'deltaZ: ' + body._dz.toFixed(2));
1029 | // debug.line('acceleration x: ' + body.acceleration.x.toFixed(2), 'y: ' + body.acceleration.y.toFixed(2), 'z: ' + body.acceleration.z.toFixed(2), 'speed: ' + body.speed.toFixed(2), 'angle: ' + body.angle.toFixed(2));
1030 | // debug.line('gravity x: ' + body.gravity.x, 'y: ' + body.gravity.y, 'z: ' + body.gravity.z);
1031 | // debug.line('bounce x: ' + body.bounce.x.toFixed(2), 'y: ' + body.bounce.y.toFixed(2), 'z: ' + body.bounce.z.toFixed(2));
1032 | // debug.line('touching: ', 'frontX: ' + (body.touching.frontX ? 1 : 0) + ' frontY: ' + (body.touching.frontY ? 1 : 0) + ' backX: ' + (body.touching.backX ? 1 : 0) + ' backY: ' + (body.touching.backY ? 1 : 0) + ' up: ' + (body.touching.up ? 1 : 0) + ' down: ' + (body.touching.down ? 1 : 0));
1033 | // debug.line('blocked: ', 'frontX: ' + (body.blocked.frontX ? 1 : 0) + ' frontY: ' + (body.blocked.frontY ? 1 : 0) + ' backX: ' + (body.blocked.backX ? 1 : 0) + ' backY: ' + (body.blocked.backY ? 1 : 0) + ' up: ' + (body.blocked.up ? 1 : 0) + ' down: ' + (body.blocked.down ? 1 : 0));
1034 | //
1035 | // };
1036 |
1037 | // Phaser.Utils.Debug.prototype.body = (function (_super) {
1038 | //
1039 | // return function (sprite, color, filled, depth) {
1040 | // if (sprite.body && sprite.body.type === ISOPHYSICS) {
1041 | // this.start();
1042 | // Body.render(this.context, sprite.body, color, filled);
1043 | // if (depth) {
1044 | // this.text(sprite.depth.toFixed(2), sprite.x, sprite.y, color, '12px Courier');
1045 | // }
1046 | // this.stop();
1047 | // }
1048 | //
1049 | // return _super.call(this, sprite, color, filled);
1050 | // };
1051 | //
1052 | // })(Phaser.Utils.Debug.prototype.body);
1053 |
1054 | // Phaser.Utils.Debug.prototype.bodyInfo = (function (_super) {
1055 | //
1056 | // return function (sprite, x, y, color) {
1057 | // if (sprite.body && sprite.body.type === ISOPHYSICS) {
1058 | // this.start(x, y, color, 210);
1059 | // Body.renderBodyInfo(this, sprite.body);
1060 | // this.stop();
1061 | // }
1062 | //
1063 | // return _super.call(this, sprite, x, y, color);
1064 | // };
1065 | //
1066 | // })(Phaser.Utils.Debug.prototype.bodyInfo);
1067 |
--------------------------------------------------------------------------------
/src/physics/World.js:
--------------------------------------------------------------------------------
1 | import Body from './Body';
2 | import Point3 from '../Point3';
3 | import Cube from '../Cube';
4 | import Octree from '../Octree';
5 |
6 | import { ISOSPRITE } from '../IsoSprite';
7 |
8 | const { GameObjects, Structs } = Phaser;
9 |
10 | export default class World {
11 | constructor(scene) {
12 |
13 | /**
14 | * Bodies
15 | *
16 | * @name World#bodies
17 | * @type {Phaser.Structs.Set.}
18 | * @since 3.0.0
19 | */
20 | this.bodies = new Structs.Set();
21 |
22 | /**
23 | * @property {Cube} bounds - The bounds inside of which the physics world exists. Defaults to match the world bounds relatively closely given the isometric projection.
24 | */
25 | const { width, height } = scene.sys.game.config;
26 | this.bounds = new Cube(0, 0, 0, width * 0.5, width * 0.5, height);
27 |
28 | /**
29 | * @property {Point3} gravity - The World gravity setting. Defaults to x: 0, y: 0, z: 0 or no gravity.
30 | */
31 | this.gravity = new Point3();
32 |
33 | /**
34 | * Set the checkCollision properties to control for which bounds collision is processed.
35 | * For example checkCollision.down = false means Bodies cannot collide with the World.bounds.bottom.
36 | * @property {object} checkCollision - An object containing allowed collision flags.
37 | */
38 | this.checkCollision = {
39 | up: true,
40 | down: true,
41 | frontX: true,
42 | frontY: true,
43 | backX: true,
44 | backY: true
45 | };
46 |
47 | /**
48 | * @property {number} OVERLAP_BIAS - A value added to the delta values during collision checks.
49 | */
50 | this.OVERLAP_BIAS = 4;
51 |
52 | /**
53 | * @property {boolean} forceX - If true World.separate will always separate on the X and Y axes before Z. Otherwise it will check gravity totals first.
54 | */
55 | this.forceXY = false;
56 |
57 | /**
58 | * @property {boolean} skipTree - If true an Octree will never be used for any collision. Handy for tightly packed games. See also Body.skipTree.
59 | */
60 | this.skipTree = false;
61 |
62 | /**
63 | * @property {number} maxObjects - Used by the Octree to set the maximum number of objects per quad.
64 | */
65 | this.maxObjects = 10;
66 |
67 | /**
68 | * @property {number} maxLevels - Used by the Octree to set the maximum number of iteration levels.
69 | */
70 | this.maxLevels = 4;
71 |
72 | /**
73 | * @property {Octree} octree - The world Octree.
74 | */
75 | this.octree = new Octree(
76 | this.bounds.x,
77 | this.bounds.y,
78 | this.bounds.z,
79 | this.bounds.widthX,
80 | this.bounds.widthY,
81 | this.bounds.height,
82 | this.maxObjects,
83 | this.maxLevels
84 | );
85 |
86 | // Avoid gc spikes by caching these values for re-use
87 |
88 | /**
89 | * @property {number} _overlap - Internal cache var.
90 | * @private
91 | */
92 | this._overlap = 0;
93 |
94 | /**
95 | * @property {number} _maxOverlap - Internal cache var.
96 | * @private
97 | */
98 | this._maxOverlap = 0;
99 |
100 | /**
101 | * @property {number} _velocity1 - Internal cache var.
102 | * @private
103 | */
104 | this._velocity1 = 0;
105 |
106 | /**
107 | * @property {number} _velocity2 - Internal cache var.
108 | * @private
109 | */
110 | this._velocity2 = 0;
111 |
112 | /**
113 | * @property {number} _newVelocity1 - Internal cache var.
114 | * @private
115 | */
116 | this._newVelocity1 = 0;
117 |
118 | /**
119 | * @property {number} _newVelocity2 - Internal cache var.
120 | * @private
121 | */
122 | this._newVelocity2 = 0;
123 |
124 | /**
125 | * @property {number} _average - Internal cache var.
126 | * @private
127 | */
128 | this._average = 0;
129 |
130 | /**
131 | * @property {Array} _mapData - Internal cache var.
132 | * @private
133 | */
134 | this._mapData = [];
135 |
136 | /**
137 | * @property {boolean} _result - Internal cache var.
138 | * @private
139 | */
140 | this._result = false;
141 |
142 | /**
143 | * @property {number} _total - Internal cache var.
144 | * @private
145 | */
146 | this._total = 0;
147 |
148 | /**
149 | * @property {number} _angle - Internal cache var.
150 | * @private
151 | */
152 | this._angle = 0;
153 |
154 | /**
155 | * @property {number} _dx - Internal cache var.
156 | * @private
157 | */
158 | this._dx = 0;
159 |
160 | /**
161 | * @property {number} _dy - Internal cache var.
162 | * @private
163 | */
164 | this._dy = 0;
165 |
166 | /**
167 | * @property {number} _dz - Internal cache var.
168 | * @private
169 | */
170 | this._dz = 0;
171 | }
172 |
173 | /**
174 | * This will create an IsoPhysics Physics body on the given game object or array of game objects.
175 | * A game object can only have 1 physics body active at any one time, and it can't be changed until the object is destroyed.
176 | *
177 | * @method IsoPhysics#enable
178 | * @param {object|array|Phaser.Group} object - The game object to create the physics body on. Can also be an array or Group of objects, a body will be created on every child that has a `body` property.
179 | * @param {boolean} [children=true] - Should a body be created on all children of this object? If true it will recurse down the display list as far as it can go.
180 | */
181 | enable(object, children = true) {
182 | var i = 1;
183 |
184 | if (Array.isArray(object)) {
185 | i = object.length;
186 |
187 | while (i--) {
188 | if (object[i] instanceof GameObjects.Group) {
189 | // If it's a Group then we do it on the children regardless
190 | this.enable(object[i].children, children);
191 | } else {
192 | this.enableBody(object[i]);
193 |
194 | if (children && object[i].hasOwnProperty('children') && object[i].children.length > 0) {
195 | this.enable(object[i], true);
196 | }
197 | }
198 | }
199 | } else if (object instanceof GameObjects.Group) {
200 | // If it's a Group then we do it on the children regardless
201 | this.enable(object.children, children);
202 | } else {
203 | this.enableBody(object);
204 |
205 | if (children && object.hasOwnProperty('children') && object.children.length > 0) {
206 | this.enable(object.children, true);
207 | }
208 | }
209 | }
210 |
211 | /**
212 | * Creates an IsoPhysics Physics body on the given game object.
213 | * A game object can only have 1 physics body active at any one time, and it can't be changed until the body is nulled.
214 | *
215 | * @method IsoPhysics#enableBody
216 | * @param {object} object - The game object to create the physics body on. A body will only be created if this object has a null `body` property.
217 | */
218 | enableBody(object) {
219 | if (object.body === null) {
220 | object.body = new Body(object);
221 | this.bodies.set(object.body);
222 | }
223 |
224 | return object;
225 | }
226 |
227 | /**
228 | * Updates the size of this physics world.
229 | *
230 | * @method IsoPhysics#setBounds
231 | * @param {number} x - Bottom rear most corner of the world.
232 | * @param {number} y - Bottom rear most corner of the world.
233 | * @param {number} z - Bottom rear most corner of the world.
234 | * @param {number} widthX - New X width (breadth) of the world. Can never be smaller than the Game.width.
235 | * @param {number} widthY - New Y width (depth) of the world. Can never be smaller than the Game.width.
236 | * @param {number} height - New height of the world. Can never be smaller than the Game.height.
237 | */
238 | setBounds(x, y, z, widthX, widthY, height) {
239 | this.bounds.setTo(x, y, z, widthX, widthY, height);
240 | }
241 |
242 | /**
243 | * Updates the size of this physics world to match the size of the game world.
244 | *
245 | * @method IsoPhysics#setBoundsToWorld
246 | */
247 | setBoundsToWorld() {
248 | const { width, height } = this.scene.sys.game.config;
249 | this.bounds.setTo(0, 0, 0, width * 0.5, width * 0.5, height);
250 | }
251 |
252 | /**
253 | * A tween-like function that takes a starting velocity and some other factors and returns an altered velocity.
254 | * Based on a function in Flixel by @ADAMATOMIC
255 | *
256 | * @method World#computeVelocity
257 | * @param {number} axis - 0 for nothing, 1 for X-axis, 2 for Y-axis, 3 for vertical (Z-axis).
258 | * @param {Body} body - The Body object to be updated.
259 | * @param {number} velocity - Any component of velocity (e.g. 20).
260 | * @param {number} acceleration - Rate at which the velocity is changing.
261 | * @param {number} drag - Really kind of a deceleration, this is how much the velocity changes if Acceleration is not set.
262 | * @param {number} [max=10000] - An absolute value cap for the velocity.
263 | * @return {number} The altered Velocity value.
264 | */
265 | computeVelocity(axis, body, velocity, acceleration, drag, max, delta) {
266 | max = max || 10000;
267 |
268 | if (axis === 1 && body.allowGravity) {
269 | velocity += (this.gravity.x + body.gravity.x) * delta;
270 | } else if (axis === 2 && body.allowGravity) {
271 | velocity += (this.gravity.y + body.gravity.y) * delta;
272 | } else if (axis === 3 && body.allowGravity) {
273 | velocity += (this.gravity.z + body.gravity.z) * delta;
274 | }
275 |
276 | if (acceleration) {
277 | velocity += acceleration * delta;
278 | } else if (drag) {
279 | this._drag = drag * delta;
280 |
281 | if (velocity - this._drag > 0) {
282 | velocity -= this._drag;
283 | } else if (velocity + this._drag < 0) {
284 | velocity += this._drag;
285 | } else {
286 | velocity = 0;
287 | }
288 | }
289 |
290 | if (velocity > max) {
291 | velocity = max;
292 | } else if (velocity < -max) {
293 | velocity = -max;
294 | }
295 |
296 | return velocity;
297 | }
298 |
299 | /**
300 | * The core separation function to separate two physics bodies.
301 | *
302 | * @private
303 | * @method IsoPhysics#separate
304 | * @param {Body} body1 - The first Body object to separate.
305 | * @param {Body} body2 - The second Body object to separate.
306 | * @param {function} [processCallback=null] - A callback function that lets you perform additional checks against the two objects if they overlap. If this function is set then the sprites will only be collided if it returns true.
307 | * @param {object} [callbackContext] - The context in which to run the process callback.
308 | * @param {boolean} overlapOnly - Just run an overlap or a full collision.
309 | * @return {boolean} Returns true if the bodies collided, otherwise false.
310 | */
311 | separate(body1, body2, processCallback, callbackContext, overlapOnly) {
312 | if (!body1.enable || !body2.enable || !this.intersects(body1, body2)) {
313 | return false;
314 | }
315 |
316 | // They overlap. Is there a custom process callback? If it returns true then we can carry on, otherwise we should abort.
317 | if (processCallback && processCallback.call(callbackContext, body1.sprite, body2.sprite) === false) {
318 | return false;
319 | }
320 |
321 | if (overlapOnly) {
322 | // We already know they intersect from the check above, and we don't need separation, so ...
323 | return true;
324 | }
325 |
326 | // Do we separate on X and Y first?
327 | // If we weren't having to carry around so much legacy baggage with us, we could do this properly. But alas ...
328 | if (this.forceXY || Math.abs(this.gravity.z + body1.gravity.z) < Math.abs(this.gravity.x + body1.gravity.x) || Math.abs(this.gravity.z + body1.gravity.z) < Math.abs(this.gravity.y + body1.gravity.y)) {
329 | this._result = (this.separateX(body1, body2, overlapOnly) || this.separateY(body1, body2, overlapOnly) || this.separateZ(body1, body2, overlapOnly));
330 | } else {
331 | this._result = (this.separateZ(body1, body2, overlapOnly) || this.separateX(body1, body2, overlapOnly) || this.separateY(body1, body2, overlapOnly));
332 | }
333 |
334 | return this._result;
335 | }
336 |
337 | /**
338 | * Check for intersection against two bodies.
339 | *
340 | * @method IsoPhysics#intersects
341 | * @param {Body} body1 - The Body object to check.
342 | * @param {Body} body2 - The Body object to check.
343 | * @return {boolean} True if they intersect, otherwise false.
344 | */
345 | intersects(body1, body2) {
346 | if (body1.frontX <= body2.x) {
347 | return false;
348 | }
349 |
350 | if (body1.frontY <= body2.y) {
351 | return false;
352 | }
353 |
354 | if (body1.x >= body2.frontX) {
355 | return false;
356 | }
357 |
358 | if (body1.y >= body2.frontY) {
359 | return false;
360 | }
361 |
362 | if (body1.top <= body2.z) {
363 | return false;
364 | }
365 |
366 | if (body1.z >= body2.top) {
367 | return false;
368 | }
369 |
370 | return true;
371 | }
372 |
373 | /**
374 | * The core separation function to separate two physics bodies on the x axis.
375 | *
376 | * @private
377 | * @method IsoPhysics#separateX
378 | * @param {Body} body1 - The Body object to separate.
379 | * @param {Body} body2 - The Body object to separate.
380 | * @param {boolean} overlapOnly - If true the bodies will only have their overlap data set, no separation or exchange of velocity will take place.
381 | * @return {boolean} Returns true if the bodies were separated, otherwise false.
382 | */
383 | separateX(body1, body2, overlapOnly) {
384 | // Can't separate two immovable bodies
385 | if (body1.immovable && body2.immovable) {
386 | return false;
387 | }
388 |
389 | this._overlap = 0;
390 |
391 | this._maxOverlap = body1.deltaAbsX() + body2.deltaAbsX() + this.OVERLAP_BIAS;
392 |
393 | if (body1.deltaX() === 0 && body2.deltaX() === 0) {
394 | // They overlap but neither of them are moving
395 | body1.embedded = true;
396 | body2.embedded = true;
397 | } else if (body1.deltaX() > body2.deltaX()) {
398 | // Body1 is moving forward and/or Body2 is moving back
399 | this._overlap = body1.frontX - body2.x;
400 |
401 | if ((this._overlap > this._maxOverlap) || body1.checkCollision.frontX === false || body2.checkCollision.backX === false) {
402 | this._overlap = 0;
403 | } else {
404 | body1.touching.none = false;
405 | body1.touching.frontX = true;
406 | body2.touching.none = false;
407 | body2.touching.backX = true;
408 | }
409 | } else if (body1.deltaX() < body2.deltaX()) {
410 | // Body1 is moving back and/or Body2 is moving forward
411 | this._overlap = body1.x - body2.widthX - body2.x;
412 |
413 | if ((-this._overlap > this._maxOverlap) || body1.checkCollision.backX === false || body2.checkCollision.frontX === false) {
414 | this._overlap = 0;
415 | } else {
416 | body1.touching.none = false;
417 | body1.touching.backX = true;
418 | body2.touching.none = false;
419 | body2.touching.frontX = true;
420 | }
421 | }
422 |
423 | // Then adjust their positions and velocities accordingly (if there was any overlap)
424 | if (this._overlap !== 0) {
425 | body1.overlapX = this._overlap;
426 | body2.overlapX = this._overlap;
427 |
428 | if (overlapOnly || body1.customSeparateX || body2.customSeparateX) {
429 | return true;
430 | }
431 |
432 | this._velocity1 = body1.velocity.x;
433 | this._velocity2 = body2.velocity.x;
434 |
435 | if (!body1.immovable && !body2.immovable) {
436 | this._overlap *= 0.5;
437 |
438 | body1.x = body1.x - this._overlap;
439 | body2.x += this._overlap;
440 |
441 | this._newVelocity1 = Math.sqrt((this._velocity2 * this._velocity2 * body2.mass) / body1.mass) * ((this._velocity2 > 0) ? 1 : -1);
442 | this._newVelocity2 = Math.sqrt((this._velocity1 * this._velocity1 * body1.mass) / body2.mass) * ((this._velocity1 > 0) ? 1 : -1);
443 | this._average = (this._newVelocity1 + this._newVelocity2) * 0.5;
444 | this._newVelocity1 -= this._average;
445 | this._newVelocity2 -= this._average;
446 |
447 | body1.velocity.x = this._average + this._newVelocity1 * body1.bounce.x;
448 | body2.velocity.x = this._average + this._newVelocity2 * body2.bounce.x;
449 | } else if (!body1.immovable) {
450 | body1.x = body1.x - this._overlap;
451 | body1.velocity.x = this._velocity2 - this._velocity1 * body1.bounce.x;
452 | } else if (!body2.immovable) {
453 | body2.x += this._overlap;
454 | body2.velocity.x = this._velocity1 - this._velocity2 * body2.bounce.x;
455 | }
456 |
457 | return true;
458 | }
459 |
460 | return false;
461 | }
462 |
463 | /**
464 | * The core separation function to separate two physics bodies on the x axis.
465 | *
466 | * @private
467 | * @method IsoPhysics#separateY
468 | * @param {Body} body1 - The Body object to separate.
469 | * @param {Body} body2 - The Body object to separate.
470 | * @param {boolean} overlapOnly - If true the bodies will only have their overlap data set, no separation or exchange of velocity will take place.
471 | * @return {boolean} Returns true if the bodies were separated, otherwise false.
472 | */
473 | separateY(body1, body2, overlapOnly) {
474 | // Can't separate two immovable bodies
475 | if (body1.immovable && body2.immovable) {
476 | return false;
477 | }
478 |
479 | this._overlap = 0;
480 |
481 | this._maxOverlap = body1.deltaAbsY() + body2.deltaAbsY() + this.OVERLAP_BIAS;
482 |
483 | if (body1.deltaY() === 0 && body2.deltaY() === 0) {
484 | // They overlap but neither of them are moving
485 | body1.embedded = true;
486 | body2.embedded = true;
487 | } else if (body1.deltaY() > body2.deltaY()) {
488 | // Body1 is moving forward and/or Body2 is moving back
489 | this._overlap = body1.frontY - body2.y;
490 |
491 | if ((this._overlap > this._maxOverlap) || body1.checkCollision.frontY === false || body2.checkCollision.backY === false) {
492 | this._overlap = 0;
493 | } else {
494 | body1.touching.none = false;
495 | body1.touching.frontY = true;
496 | body2.touching.none = false;
497 | body2.touching.backY = true;
498 | }
499 | } else if (body1.deltaY() < body2.deltaY()) {
500 | // Body1 is moving back and/or Body2 is moving forward
501 | this._overlap = body1.y - body2.widthY - body2.y;
502 |
503 | if ((-this._overlap > this._maxOverlap) || body1.checkCollision.backY === false || body2.checkCollision.frontY === false) {
504 | this._overlap = 0;
505 | } else {
506 | body1.touching.none = false;
507 | body1.touching.backY = true;
508 | body2.touching.none = false;
509 | body2.touching.frontY = true;
510 | }
511 | }
512 |
513 | // Then adjust their positions and velocities accordingly (if there was any overlap)
514 | if (this._overlap !== 0) {
515 | body1.overlapY = this._overlap;
516 | body2.overlapY = this._overlap;
517 |
518 | if (overlapOnly || body1.customSeparateY || body2.customSeparateY) {
519 | return true;
520 | }
521 |
522 | this._velocity1 = body1.velocity.y;
523 | this._velocity2 = body2.velocity.y;
524 |
525 | if (!body1.immovable && !body2.immovable) {
526 | this._overlap *= 0.5;
527 |
528 | body1.y = body1.y - this._overlap;
529 | body2.y += this._overlap;
530 |
531 | this._newVelocity1 = Math.sqrt((this._velocity2 * this._velocity2 * body2.mass) / body1.mass) * ((this._velocity2 > 0) ? 1 : -1);
532 | this._newVelocity2 = Math.sqrt((this._velocity1 * this._velocity1 * body1.mass) / body2.mass) * ((this._velocity1 > 0) ? 1 : -1);
533 | this._average = (this._newVelocity1 + this._newVelocity2) * 0.5;
534 | this._newVelocity1 -= this._average;
535 | this._newVelocity2 -= this._average;
536 |
537 | body1.velocity.y = this._average + this._newVelocity1 * body1.bounce.y;
538 | body2.velocity.y = this._average + this._newVelocity2 * body2.bounce.y;
539 | } else if (!body1.immovable) {
540 | body1.y = body1.y - this._overlap;
541 | body1.velocity.y = this._velocity2 - this._velocity1 * body1.bounce.y;
542 | } else if (!body2.immovable) {
543 | body2.y += this._overlap;
544 | body2.velocity.y = this._velocity1 - this._velocity2 * body2.bounce.y;
545 | }
546 |
547 | return true;
548 | }
549 |
550 | return false;
551 | }
552 |
553 | /**
554 | * The core separation function to separate two physics bodies on the z axis.
555 | *
556 | * @private
557 | * @method IsoPhysics#separateZ
558 | * @param {Body} body1 - The Body object to separate.
559 | * @param {Body} body2 - The Body object to separate.
560 | * @param {boolean} overlapOnly - If true the bodies will only have their overlap data set, no separation or exchange of velocity will take place.
561 | * @return {boolean} Returns true if the bodies were separated, otherwise false.
562 | */
563 | separateZ(body1, body2, overlapOnly) {
564 | // Can't separate two immovable or non-existing bodys
565 | if (body1.immovable && body2.immovable) {
566 | return false;
567 | }
568 |
569 | this._overlap = 0;
570 |
571 | this._maxOverlap = body1.deltaAbsZ() + body2.deltaAbsZ() + this.OVERLAP_BIAS;
572 |
573 | if (body1.deltaZ() === 0 && body2.deltaZ() === 0) {
574 | // They overlap but neither of them are moving
575 | body1.embedded = true;
576 | body2.embedded = true;
577 | } else if (body1.deltaZ() > body2.deltaZ()) {
578 | // Body1 is moving down and/or Body2 is moving up
579 | this._overlap = body1.top - body2.z;
580 |
581 | if ((this._overlap > this._maxOverlap) || body1.checkCollision.down === false || body2.checkCollision.up === false) {
582 | this._overlap = 0;
583 | } else {
584 | body1.touching.none = false;
585 | body1.touching.down = true;
586 | body2.touching.none = false;
587 | body2.touching.up = true;
588 | }
589 | } else if (body1.deltaZ() < body2.deltaZ()) {
590 | // Body1 is moving up and/or Body2 is moving down
591 | this._overlap = body1.z - body2.top;
592 |
593 | if ((-this._overlap > this._maxOverlap) || body1.checkCollision.up === false || body2.checkCollision.down === false) {
594 | this._overlap = 0;
595 | } else {
596 | body1.touching.none = false;
597 | body1.touching.up = true;
598 | body2.touching.none = false;
599 | body2.touching.down = true;
600 | }
601 | }
602 |
603 | // Then adjust their positions and velocities accordingly (if there was any overlap)
604 | if (this._overlap !== 0) {
605 | body1.overlapZ = this._overlap;
606 | body2.overlapZ = this._overlap;
607 |
608 | if (overlapOnly || body1.customSeparateY || body2.customSeparateZ) {
609 | return true;
610 | }
611 |
612 | this._velocity1 = body1.velocity.z;
613 | this._velocity2 = body2.velocity.z;
614 |
615 | if (!body1.immovable && !body2.immovable) {
616 | this._overlap *= 0.5;
617 |
618 | body1.z = body1.z - this._overlap;
619 | body2.z += this._overlap;
620 |
621 | this._newVelocity1 = Math.sqrt((this._velocity2 * this._velocity2 * body2.mass) / body1.mass) * ((this._velocity2 > 0) ? 1 : -1);
622 | this._newVelocity2 = Math.sqrt((this._velocity1 * this._velocity1 * body1.mass) / body2.mass) * ((this._velocity1 > 0) ? 1 : -1);
623 | this._average = (this._newVelocity1 + this._newVelocity2) * 0.5;
624 | this._newVelocity1 -= this._average;
625 | this._newVelocity2 -= this._average;
626 |
627 | body1.velocity.z = this._average + this._newVelocity1 * body1.bounce.z;
628 | body2.velocity.z = this._average + this._newVelocity2 * body2.bounce.z;
629 | } else if (!body1.immovable) {
630 | body1.z = body1.z - this._overlap;
631 | body1.velocity.z = this._velocity2 - this._velocity1 * body1.bounce.z;
632 |
633 | // This is special case code that handles things like moving platforms you can ride
634 | if (body2.moves) {
635 | body1.x += body2.x - body2.prev.x;
636 | body1.y += body2.y - body2.prev.y;
637 | }
638 | } else if (!body2.immovable) {
639 | body2.z += this._overlap;
640 | body2.velocity.z = this._velocity1 - this._velocity2 * body2.bounce.z;
641 |
642 | // This is special case code that handles things like moving platforms you can ride
643 | if (body1.moves) {
644 | body2.x += body1.x - body1.prev.x;
645 | body2.y += body1.y - body1.prev.y;
646 | }
647 | }
648 |
649 | return true;
650 | }
651 |
652 | return false;
653 | }
654 |
655 | /**
656 | * Checks for overlaps between two game objects. The objects can be IsoSprites or Groups.
657 | * You can perform IsoSprite vs. IsoSprite, IsoSprite vs. Group and Group vs. Group overlap checks.
658 | * Unlike collide the objects are NOT automatically separated or have any physics applied, they merely test for overlap results.
659 | * The second parameter can be an array of objects, of differing types.
660 | * NOTE: This function is not recursive, and will not test against children of objects passed (i.e. Groups within Groups).
661 | *
662 | * @method IsoPhysics#overlap
663 | * @param {IsoSprite|Phaser.Group} object1 - The first object to check. Can be an instance of IsoSprite or Phaser.Group.
664 | * @param {IsoSprite|Phaser.Group|array} object2 - The second object or array of objects to check. Can be IsoSprite or Phaser.Group.
665 | * @param {function} [overlapCallback=null] - An optional callback function that is called if the objects overlap. The two objects will be passed to this function in the same order in which you specified them.
666 | * @param {function} [processCallback=null] - A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then overlapCallback will only be called if processCallback returns true.
667 | * @param {object} [callbackContext] - The context in which to run the callbacks.
668 | * @return {boolean} True if an overlap occured otherwise false.
669 | */
670 | overlap(object1, object2, overlapCallback = null, processCallback = null, callbackContext) {
671 | callbackContext = callbackContext || overlapCallback;
672 |
673 | this._result = false;
674 | this._total = 0;
675 |
676 | if (Array.isArray(object2)) {
677 | for (var i = 0, len = object2.length; i < len; i++) {
678 | this.collideHandler(object1, object2[i], overlapCallback, processCallback, callbackContext, true);
679 | }
680 | } else {
681 | this.collideHandler(object1, object2, overlapCallback, processCallback, callbackContext, true);
682 | }
683 |
684 | return (this._total > 0);
685 | }
686 |
687 | /**
688 | * Checks for collision between two game objects. You can perform IsoSprite vs. IsoSprite, IsoSprite vs. Group or Group vs. Group collisions.
689 | * The second parameter can be an array of objects, of differing types.
690 | * The objects are also automatically separated. If you don't require separation then use IsoPhysics.overlap instead.
691 | * An optional processCallback can be provided. If given this function will be called when two sprites are found to be colliding. It is called before any separation takes place,
692 | * giving you the chance to perform additional checks. If the function returns true then the collision and separation is carried out. If it returns false it is skipped.
693 | * The collideCallback is an optional function that is only called if two sprites collide. If a processCallback has been set then it needs to return true for collideCallback to be called.
694 | * NOTE: This function is not recursive, and will not test against children of objects passed (i.e. Groups within Groups).
695 | *
696 | * @method IsoPhysics#collide
697 | * @param {IsoSprite|Phaser.Group} object1 - The first object to check. Can be an instance of IsoSprite or Phaser.Group.
698 | * @param {IsoSprite|Phaser.Group|array} object2 - The second object or array of objects to check. Can be IsoSprite or Phaser.Group.
699 | * @param {function} [collideCallback=null] - An optional callback function that is called if the objects collide. The two objects will be passed to this function in the same order in which you specified them, unless you are colliding Group vs. Sprite, in which case Sprite will always be the first parameter.
700 | * @param {function} [processCallback=null] - A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then collision will only happen if processCallback returns true. The two objects will be passed to this function in the same order in which you specified them.
701 | * @param {object} [callbackContext] - The context in which to run the callbacks.
702 | * @return {boolean} True if a collision occured otherwise false.
703 | */
704 | collide(object1, object2, collideCallback = null, processCallback = null, callbackContext) {
705 | callbackContext = callbackContext || collideCallback;
706 |
707 | this._result = false;
708 | this._total = 0;
709 |
710 | if (Array.isArray(object2)) {
711 | for (var i = 0, len = object2.length; i < len; i++) {
712 | this.collideHandler(object1, object2[i], collideCallback, processCallback, callbackContext, false);
713 | }
714 | }
715 | else {
716 | this.collideHandler(object1, object2, collideCallback, processCallback, callbackContext, false);
717 | }
718 |
719 | return (this._total > 0);
720 | }
721 |
722 | /**
723 | * Internal collision handler.
724 | *
725 | * @method IsoPhysics#collideHandler
726 | * @private
727 | * @param {IsoSprite|Phaser.Group} object1 - The first object to check. Can be an instance of IsoSprite or Phaser.Group.
728 | * @param {IsoSprite|Phaser.Group} object2 - The second object to check. Can be an instance of IsoSprite or Phaser.Group. Can also be an array of objects to check.
729 | * @param {function} collideCallback - An optional callback function that is called if the objects collide. The two objects will be passed to this function in the same order in which you specified them.
730 | * @param {function} processCallback - A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then collision will only happen if processCallback returns true. The two objects will be passed to this function in the same order in which you specified them.
731 | * @param {object} callbackContext - The context in which to run the callbacks.
732 | * @param {boolean} overlapOnly - Just run an overlap or a full collision.
733 | */
734 | collideHandler(object1, object2, collideCallback, processCallback, callbackContext, overlapOnly) {
735 | // Only collide valid objects
736 | if (!object2 && object1.type === Phaser.GROUP) {
737 | this.collideGroupVsSelf(object1, collideCallback, processCallback, callbackContext, overlapOnly);
738 | return;
739 | }
740 |
741 | if (object1 && object2) {
742 | // ISOSPRITES
743 | if (object1.type === ISOSPRITE) {
744 | if (object2.type === ISOSPRITE) {
745 | this.collideSpriteVsSprite(object1, object2, collideCallback, processCallback, callbackContext, overlapOnly);
746 | } else if (object2.type === Phaser.GROUP) {
747 | this.collideSpriteVsGroup(object1, object2, collideCallback, processCallback, callbackContext, overlapOnly);
748 | }
749 | }
750 | // GROUPS
751 | else if (object1.type === Phaser.GROUP) {
752 | if (object2.type === ISOSPRITE) {
753 | this.collideSpriteVsGroup(object2, object1, collideCallback, processCallback, callbackContext, overlapOnly);
754 | } else if (object2.type === Phaser.GROUP) {
755 | this.collideGroupVsGroup(object1, object2, collideCallback, processCallback, callbackContext, overlapOnly);
756 | }
757 | }
758 | }
759 | }
760 |
761 | /**
762 | * An internal function. Use IsoPhysics.collide instead.
763 | *
764 | * @method IsoPhysics#collideSpriteVsSprite
765 | * @private
766 | * @param {IsoSprite} sprite1 - The first sprite to check.
767 | * @param {IsoSprite} sprite2 - The second sprite to check.
768 | * @param {function} collideCallback - An optional callback function that is called if the objects collide. The two objects will be passed to this function in the same order in which you specified them.
769 | * @param {function} processCallback - A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then collision will only happen if processCallback returns true. The two objects will be passed to this function in the same order in which you specified them.
770 | * @param {object} callbackContext - The context in which to run the callbacks.
771 | * @param {boolean} overlapOnly - Just run an overlap or a full collision.
772 | * @return {boolean} True if there was a collision, otherwise false.
773 | */
774 | collideSpriteVsSprite(sprite1, sprite2, collideCallback, processCallback, callbackContext, overlapOnly) {
775 | if (!sprite1.body || !sprite2.body) { return false; }
776 |
777 | if (this.separate(sprite1.body, sprite2.body, processCallback, callbackContext, overlapOnly)) {
778 | if (collideCallback) {
779 | collideCallback.call(callbackContext, sprite1, sprite2);
780 | }
781 |
782 | this._total++;
783 | }
784 |
785 | return true;
786 | }
787 |
788 | /**
789 | * An internal function. Use IsoPhysics.collide instead.
790 | *
791 | * @method IsoPhysics#collideSpriteVsGroup
792 | * @private
793 | * @param {IsoSprite} sprite - The sprite to check.
794 | * @param {Phaser.Group} group - The Group to check.
795 | * @param {function} collideCallback - An optional callback function that is called if the objects collide. The two objects will be passed to this function in the same order in which you specified them.
796 | * @param {function} processCallback - A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then collision will only happen if processCallback returns true. The two objects will be passed to this function in the same order in which you specified them.
797 | * @param {object} callbackContext - The context in which to run the callbacks.
798 | * @param {boolean} overlapOnly - Just run an overlap or a full collision.
799 | */
800 | collideSpriteVsGroup(sprite, group, collideCallback, processCallback, callbackContext, overlapOnly) {
801 | var i, len;
802 |
803 | if (group.children.size === 0 || !sprite.body) { return; }
804 |
805 | if (sprite.body.skipTree || this.skipTree) {
806 | for (i = 0, len = group.children.size; i < len; i++) {
807 | const child = group.children.entries[i];
808 | if (child) {
809 | this.collideSpriteVsSprite(sprite, child, collideCallback, processCallback, callbackContext, overlapOnly);
810 | }
811 | }
812 | } else {
813 | // What is the sprite colliding with in the octree?
814 | this.octree.clear();
815 |
816 | this.octree.reset(this.bounds.x, this.bounds.y, this.bounds.z, this.bounds.widthX, this.bounds.widthY, this.bounds.height, this.maxObjects, this.maxLevels);
817 |
818 | this.octree.populate(group);
819 |
820 | this._potentials = this.octree.retrieve(sprite);
821 |
822 | for (i = 0, len = this._potentials.length; i < len; i++) {
823 | // We have our potential suspects, are they in this group?
824 | if (this.separate(sprite.body, this._potentials[i], processCallback, callbackContext, overlapOnly)) {
825 | if (collideCallback) {
826 | collideCallback.call(callbackContext, sprite, this._potentials[i].sprite);
827 | }
828 |
829 | this._total++;
830 | }
831 | }
832 | }
833 | }
834 |
835 | /**
836 | * An internal function. Use IsoPhysics.collide instead.
837 | *
838 | * @method IsoPhysics#collideGroupVsSelf
839 | * @private
840 | * @param {Phaser.Group} group - The Group to check.
841 | * @param {function} collideCallback - An optional callback function that is called if the objects collide. The two objects will be passed to this function in the same order in which you specified them.
842 | * @param {function} processCallback - A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then collision will only happen if processCallback returns true. The two objects will be passed to this function in the same order in which you specified them.
843 | * @param {object} callbackContext - The context in which to run the callbacks.
844 | * @param {boolean} overlapOnly - Just run an overlap or a full collision.
845 | * @return {boolean} True if there was a collision, otherwise false.
846 | */
847 | collideGroupVsSelf(group, collideCallback, processCallback, callbackContext, overlapOnly) {
848 | if (group.children.size === 0) { return; }
849 |
850 | var len = group.children.size;
851 |
852 | for (var i = 0; i < len; i++) {
853 | for (var j = i + 1; j <= len; j++) {
854 | const entries = group.children.entries;
855 | const spriteOne = entries[i];
856 | const spriteTwo = entries[j];
857 |
858 | if (spriteOne && spriteTwo) {
859 | this.collideSpriteVsSprite(
860 | spriteOne,
861 | spriteTwo,
862 | collideCallback,
863 | processCallback,
864 | callbackContext,
865 | overlapOnly
866 | );
867 | }
868 | }
869 | }
870 | }
871 |
872 | /**
873 | * An internal function. Use IsoPhysics.collide instead.
874 | *
875 | * @method IsoPhysics#collideGroupVsGroup
876 | * @private
877 | * @param {Phaser.Group} group1 - The first Group to check.
878 | * @param {Phaser.Group} group2 - The second Group to check.
879 | * @param {function} collideCallback - An optional callback function that is called if the objects collide. The two objects will be passed to this function in the same order in which you specified them.
880 | * @param {function} processCallback - A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then collision will only happen if processCallback returns true. The two objects will be passed to this function in the same order in which you specified them.
881 | * @param {object} callbackContext - The context in which to run the callbacks.
882 | * @param {boolean} overlapOnly - Just run an overlap or a full collision.
883 | */
884 | collideGroupVsGroup(group1, group2, collideCallback, processCallback, callbackContext, overlapOnly) {
885 | if (group1.children.size === 0 || group2.children.size === 0) { return; }
886 |
887 | for (var i = 0, len = group1.children.size; i < len; i++) {
888 | this.collideSpriteVsGroup(group1.children.entries[i], group2, collideCallback, processCallback, callbackContext, overlapOnly);
889 | }
890 | }
891 |
892 | /**
893 | * Called automatically by a Physics body, it updates all motion related values on the Body.
894 | *
895 | * @method IsoPhysics#updateMotion
896 | * @param {Body} body - The Body object to be updated.
897 | */
898 | updateMotion(body, delta) {
899 | this._velocityDelta = this.computeVelocity(0, body, body.angularVelocity, body.angularAcceleration, body.angularDrag, body.maxAngular) - body.angularVelocity;
900 | body.angularVelocity += this._velocityDelta;
901 | body.rotation += (body.angularVelocity * delta);
902 |
903 | body.velocity.x = this.computeVelocity(1, body, body.velocity.x, body.acceleration.x, body.drag.x, body.maxVelocity.x, delta);
904 | body.velocity.y = this.computeVelocity(2, body, body.velocity.y, body.acceleration.y, body.drag.y, body.maxVelocity.y, delta);
905 | body.velocity.z = this.computeVelocity(3, body, body.velocity.z, body.acceleration.z, body.drag.z, body.maxVelocity.z, delta);
906 | }
907 |
908 | update(time, delta) {
909 | const bodies = this.bodies.entries;
910 | const len = bodies.length;
911 | let i;
912 |
913 | for (i = 0; i < len; i++) {
914 | const body = bodies[i];
915 | if (body.enable) {
916 | bodies[i].update(time, delta);
917 | }
918 | }
919 | }
920 |
921 | postUpdate() {
922 | const bodies = this.bodies.entries;
923 | const len = bodies.length;
924 | let i;
925 |
926 | for (i = 0; i < len; i++) {
927 | const body = bodies[i];
928 | if (body.enable) {
929 | body.postUpdate();
930 | }
931 | }
932 | }
933 | }
934 |
--------------------------------------------------------------------------------