Draw line in Phaser 3 - javascript

I need your help. I am newbie in Phaser 3. I want to create game with simple rules. There are 36 dots, which situated in 6 rows. Player needs to unite dots with a line, but he can only unite dots with same color and union can happen only vertically and horizontally. So, you can't draw daigonal line. When you will finish union, dots will vanish. How I can realize union with line? My current code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/phaser.min.js"></script>
</head>
<body>
<script>
let config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#f0ebeb',
physics: {
default: 'arcade',
arcade: {
gravity: { y: 300 },
debug: false
}
},
scene: {
preload: preload,
create: create,
update: update
},
scale: {
autoCenter: Phaser.Scale.CENTER_BOTH
}
};
let game = new Phaser.Game(config);
let items = [];
function preload() {
for (let i = 1; i < 6; i++)
this.load.image(i, 'img/' + i + '.png');
}
function create() {
let x = 100;
let y = 0;
for (i = 0; i < 36; i++) {
if (i % 6 === 0) {
y += 85;
x = 100;
}
this.add.image(x, y, getRandomInt(5).toString())
x += 125;
}
}
function update() { }
function getRandomInt(max) {
return Math.floor(Math.random() * max) + 1;
}
</script>
</body>
</html>
I want something like this

I would just allow the user only to select (draw a line) to next valid circles.
A valid circle being, on the same row or same column or same color or max union length or ...
And when the drawing is finished I would, only allow union, when more than one circle is selected (is in the given path).
Here a short demo, how it could look like:
let config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 400,
height: 200,
banner: false,
scene: { create }
};
let game = new Phaser.Game(config);
let isDragging = false;
let lineStartPosition = {x:0 , y:0};
let currentPath = [];
let currentColor;
let line;
let pathGraphics;
function create ()
{
let cicles = []
for(let rowIdx = 0; rowIdx < 4; rowIdx++ ){
for(let colIdx = 0; colIdx < 2; colIdx++ ){
let circle = this.add.circle(50 + 100 * rowIdx, 50 + 100 * colIdx, 25, 0x6666ff).setOrigin(.5);
// Just to test a different color
if(rowIdx + colIdx ==2){
circle.fillColor = 0xff0000;
}
circle.setInteractive();
cicles.push(circle);
}
}
line = this.add.line(0,0, 0,0, 100, 100, 0xffffff).setOrigin(0);
line.setLineWidth(5);
line.visible = false;
pathGraphics = this.add.graphics();
// adding the events to the scene
this.input.on('pointerdown', dragStart);
this.input.on('pointerup', dragEnd);
this.input.on('pointermove', drag);
}
function dragStart(pointer, gameObjects){
if(gameObjects.length == 0){
return;
}
isDragging = true;
// remember Starting Color
currentColor = gameObjects[0].fillColor;
// initialize Path
currentPath = [gameObjects[0]];
// draw/save last segment of the path
lineStartPosition.x = gameObjects[0].x;
lineStartPosition.y = gameObjects[0].y;
line.x = gameObjects[0].x;
line.y = gameObjects[0].y;
line.setTo(0, 0, 0, 0);
line.visible = true;
}
function drag(pointer, gameObjects ){
if(isDragging == true){
// Check If Circle is allowed to be added, to path
// Here you would also check if the line is horizontal or vertical (this part is currently not implemented)
if(gameObjects[0] && currentPath.indexOf(gameObjects[0]) === -1 && currentColor == gameObjects[0].fillColor){
currentPath.push(gameObjects[0]);
line.x = gameObjects[0].x;
line.y = gameObjects[0].y;
lineStartPosition.x = gameObjects[0].x;
lineStartPosition.y = gameObjects[0].y;
}
line.setTo(0, 0, pointer.x - lineStartPosition.x, pointer.y - lineStartPosition.y);
drawPath();
}
}
function drawPath(){
pathGraphics.clear();
if(currentPath.length > 0){
pathGraphics.lineStyle(10, 0xffffff);
let path = new Phaser.Curves.Path(currentPath[0].x, currentPath[0].y);
for(let idx = 1; idx < currentPath.length; idx++){
let point = currentPath[idx];
path.lineTo(point.x, point.y);
}
path.draw(pathGraphics);
}
}
function dragEnd(pointer, gameObjects){
if(gameObjects.length == 0){
return;
}
if(currentPath.length < 2) {
console.info('No valid path');
return
} else {
console.info(`Valid Union path, length: ${currentPath.length}`);
}
line.visible = false;
isDragging = false;
}
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>

Related

Freeze scrollbar

I create a game using javascript. The code is placed at the bottom of the page. Visualize it as if the game is below a footer. The game starts when the user selects the start game button. The mechanics of the game are controlled by the arrow key. Arrow keys are used to move up, right, and left down. The problem is when I press the up or down key, the page scrolls too. How to stop this? I want the page not to scroll when the game is active. I am attaching the code of the game I create. It is a snake game.There's CSS too, but I'm not attaching it as I don't think its relevant
<div id="app" class="app">
<div class="start-screen">
<h2>🦊 </h2>
<div class="options">
<h3>Choose Difficulty</h3>
<p class="end-score"></p>
<button data-difficulty="100" class="active">Easy</button>
<button data-difficulty="75">Medium</button>
<button data-difficulty="50">Hard</button>
</div>
<button class="play-btn">Play</button>
</div>
<canvas id="board"></canvas>
<div class="score">0</div>
</div>
<script>
class SnakeGame {
constructor() {
this.$app = document.querySelector('#app');
this.$canvas = this.$app.querySelector('canvas');
this.ctx = this.$canvas.getContext('2d');
this.$startScreen = this.$app.querySelector('.start-screen');
this.$score = this.$app.querySelector('.score');
this.settings = {
canvas: {
width: 500,
height: 500,
background: 'white',
border: 'black'
},
snake: {
size: 20,
background: '#73854A',
border: '#000'
}
};
this.game = {
// "direction" (set in setUpGame())
// "nextDirection" (set in setUpGame())
// "score" (set in setUpGame())
speed: 100,
keyCodes: {
38: 'up',
40: 'down',
39: 'right',
37: 'left'
}
}
this.soundEffects = {
score: new Audio('./sounds/score.mp3'),
gameOver: new Audio('./sounds/game-over.mp3')
};
this.setUpGame();
this.init();
}
init() {
// Choose difficulty
// Rather than using "this.$startScreen.querySelectorAll('button')" and looping over the node list
// and attaching seperate event listeners on each item, it's more efficient to just listen in on the container and run a check at runtime
this.$startScreen.querySelector('.options').addEventListener('click', event => {
this.chooseDifficulty(event.target.dataset.difficulty);
});
// Play
this.$startScreen.querySelector('.play-btn').addEventListener('click', () => {
this.startGame();
});
}
chooseDifficulty(difficulty) {
if(difficulty) {
this.game.speed = difficulty;
this.$startScreen.querySelectorAll('.options button').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
}
}
setUpGame() {
// The snake starts off with 5 pieces
// Each piece is 30x30 pixels
// Each following piece must be n times as far from the first piece
const x = 300;
const y = 300;
this.snake = [
{ x: x, y: y },
{ x: x - this.settings.snake.size, y: y },
{ x: x - (this.settings.snake.size * 2), y: y },
{ x: x - (this.settings.snake.size * 3), y: y },
{ x: x - (this.settings.snake.size * 4), y: y }
];
this.food = {
active: false,
background: '#EC5E0B',
border: '#73AA24',
coordinates: {
x: 0,
y: 0
}
};
this.game.score = 0;
this.game.direction = 'right';
this.game.nextDirection = 'right';
}
startGame() {
// Stop the game over sound effect if a new game was restarted quickly before it could end
this.soundEffects.gameOver.pause();
this.soundEffects.gameOver.currentTime = 0;
// Reset a few things from the prior game
this.$app.classList.add('game-in-progress');
this.$app.classList.remove('game-over');
this.$score.innerText = 0;
this.generateSnake();
this.startGameInterval = setInterval(() => {
if(!this.detectCollision()) {
this.generateSnake();
} else {
this.endGame();
}
}, this.game.speed);
// Change direction
document.addEventListener('keydown', event => {
this.changeDirection(event.keyCode);
});
}
changeDirection(keyCode) {
const validKeyPress = Object.keys(this.game.keyCodes).includes(keyCode.toString()); // Only allow (up|down|left|right)
if(validKeyPress && this.validateDirectionChange(this.game.keyCodes[keyCode], this.game.direction)) {
this.game.nextDirection = this.game.keyCodes[keyCode];
}
}
// When already moving in one direction snake shouldn't be allowed to move in the opposite direction
validateDirectionChange(keyPress, currentDirection) {
return (keyPress === 'left' && currentDirection !== 'right') ||
(keyPress === 'right' && currentDirection !== 'left') ||
(keyPress === 'up' && currentDirection !== 'down') ||
(keyPress === 'down' && currentDirection !== 'up');
}
resetCanvas() {
// Full screen size
this.$canvas.width = this.settings.canvas.width;
this.$canvas.height = this.settings.canvas.height;
this.$canvas.style.border = `3px solid ${this.settings.canvas.border}`;
// Background
this.ctx.fillStyle = this.settings.canvas.background;
this.ctx.fillRect(0, 0, this.$canvas.width, this.$canvas.height);
}
generateSnake() {
let coordinate;
switch(this.game.direction) {
case 'right':
coordinate = {
x: this.snake[0].x + this.settings.snake.size,
y: this.snake[0].y
};
break;
case 'up':
coordinate = {
x: this.snake[0].x,
y: this.snake[0].y - this.settings.snake.size
};
break;
case 'left':
coordinate = {
x: this.snake[0].x - this.settings.snake.size,
y: this.snake[0].y
};
break;
case 'down':
coordinate = {
x: this.snake[0].x,
y: this.snake[0].y + this.settings.snake.size
};
}
// The snake moves by adding a piece to the beginning "this.snake.unshift(coordinate)" and removing the last piece "this.snake.pop()"
// Except when it eats the food in which case there is no need to remove a piece and the added piece will make it grow
this.snake.unshift(coordinate);
this.resetCanvas();
const ateFood = this.snake[0].x === this.food.coordinates.x && this.snake[0].y === this.food.coordinates.y;
if(ateFood) {
this.food.active = false;
this.game.score += 10;
this.$score.innerText = this.game.score;
this.soundEffects.score.play();
} else {
this.snake.pop();
}
this.generateFood();
this.drawSnake();
}
drawSnake() {
const size = this.settings.snake.size;
this.ctx.fillStyle = this.settings.snake.background;
this.ctx.strokestyle = this.settings.snake.border;
// Draw each piece
this.snake.forEach(coordinate => {
this.ctx.fillRect(coordinate.x, coordinate.y, size, size);
this.ctx.strokeRect(coordinate.x, coordinate.y, size, size);
});
this.game.direction = this.game.nextDirection;
}
generateFood() {
// If there is uneaten food on the canvas there's no need to regenerate it
if(this.food.active) {
this.drawFood(this.food.coordinates.x, this.food.coordinates.y);
return;
}
const gridSize = this.settings.snake.size;
const xMax = this.settings.canvas.width - gridSize;
const yMax = this.settings.canvas.height - gridSize;
const x = Math.round((Math.random() * xMax) / gridSize) * gridSize;
const y = Math.round((Math.random() * yMax) / gridSize) * gridSize;
// Make sure the generated coordinates do not conflict with the snake's present location
// If so recall this method recursively to try again
this.snake.forEach(coordinate => {
const foodSnakeConflict = coordinate.x == x && coordinate.y == y;
if(foodSnakeConflict) {
this.generateFood();
} else {
this.drawFood(x, y);
}
});
}
drawFood(x, y) {
const size = this.settings.snake.size;
this.ctx.fillStyle = this.food.background;
this.ctx.strokestyle = this.food.border;
this.ctx.fillRect(x, y, size, size);
this.ctx.strokeRect(x, y, size, size);
this.food.active = true;
this.food.coordinates.x = x;
this.food.coordinates.y = y;
}
detectCollision() {
// Self collison
// It's impossible for the first 3 pieces of the snake to self collide so the loop starts at 4
for(let i = 4; i < this.snake.length; i++) {
const selfCollison = this.snake[i].x === this.snake[0].x && this.snake[i].y === this.snake[0].y;
if(selfCollison) {
return true;
}
}
// Wall collison
const leftCollison = this.snake[0].x < 0;
const topCollison = this.snake[0].y < 0;
const rightCollison = this.snake[0].x > this.$canvas.width - this.settings.snake.size;
const bottomCollison = this.snake[0].y > this.$canvas.height - this.settings.snake.size;
return leftCollison || topCollison || rightCollison || bottomCollison;
}
endGame() {
this.soundEffects.gameOver.play();
clearInterval(this.startGameInterval);
this.$app.classList.remove('game-in-progress');
this.$app.classList.add('game-over');
this.$startScreen.querySelector('.options h3').innerText = 'Game Over';
this.$startScreen.querySelector('.options .end-score').innerText = `Score: ${this.game.score}`;
this.setUpGame();
}
}
const snakeGame = new SnakeGame();
</script>
Stop the page from moving up and down when the game is active
You just need to prevent the default behaviour of these keys:
// Change direction
document.addEventListener('keydown', event => {
event.preventDefault(); // add this line
this.changeDirection(event.keyCode);
});
Note that this isn't a very good idea to do it that way, as it will prevent the default behaviour of all keypresses anywhere on your page. Most notably, F5 will never refresh the browser, and if you have other elements like buttons, links, etc, the standard keypresses to activate those will stop working (turning these off is a particular problem if you want your site to be accessible to those with disabilities).
You'd be better off checking the keyCode first and only preventing default if it corresponds to one of the arrow keys - which isn't so easy to do with your code as it stands but I'd encourage you to rewrite it that way.
Even doing that presents a problem to those who are not able to use a mouse, as (depending on the position of your game interface on the screen and its size) they may still need to scroll down and will be prevented from doing this by this preventDefault behaviour. But well that's what you asked for, and I've given you the solution - but with this important caveat.

Simple game using Pixi.js optimization hints

I coded a little simulation where you can dig cubes, they fall an stack, you can consume some or make a selection explode.
I started this little project for fun and my aim is to handle as much cubes as possible (100k would be a good start).
The project is simple, 3 possibles actions:
Dig cubes (2k each click)
Consume cubes (50 each click)
Explode cubes (click on two points to form a rectangle)
Currently, on my computer, performance starts to drop when I got about 20k cubes. When you select a large portion of cubes to explode, performance are heavily slowed down too. I'm not sure the way I simplified physics is the best way to go.
Could you give me some hints on how to improve/optimize it ?
Here is the complete code : (The stacking doesn't work in the SO snippet so here is the codepen version)
(() => {
// Variables init
let defaultState = {
cubesPerDig : 2000,
cubesIncome : 0,
total : 0
};
let cubeSize = 2, dropSize = window.innerWidth, downSpeed = 5;
let state,
digButton, // Button to manually dig
gameState, // An object containing the state of the game
cubes, // Array containing all the spawned cubes
heightIndex, // fake physics
cubesPerX, // fake physics helper
playScene; // The gamescene
// App setup
let app = new PIXI.Application();
app.renderer.view.style.position = "absolute";
app.renderer.view.style.display = "block";
app.renderer.autoResize = true;
document.body.appendChild(app.view);
// Resize
function resize() {
app.renderer.resize(window.innerWidth, window.innerHeight);
}
window.onresize = resize;
resize();
// Hello ! we can talk in the chat.txt file
// Issue : When there are more than ~10k cubes, performance start to drop
// To test, click the "mine" button about 5-10 times
// Main state
function play(delta){
// Animate the cubes according to their states
let cube;
for(let c in cubes){
cube = cubes[c];
switch(cube.state) {
case STATE.LANDING:
// fake physics
if(!cube.landed){
if (cube.y < heightIndex[cube.x]) {
cube.y+= downSpeed;
}else if (cube.y >= heightIndex[cube.x]) {
cube.y = heightIndex[cube.x];
cube.landed = 1;
heightIndex[cube.x] -= cubeSize;
}
}
break;
case STATE.CONSUMING:
if(cube.y > -cubeSize){
cube.y -= cube.speed;
}else{
removeCube(c);
}
break;
case STATE.EXPLODING:
if(boundings(c)){
continue;
}
cube.x += cube.eDirX;
cube.y += cube.eDirY;
break;
}
}
updateUI();
}
// Game loop
function gameLoop(delta){
state(delta);
}
// Setup variables and gameState
function setup(){
state = play;
digButton = document.getElementById('dig');
digButton.addEventListener('click', mine);
playScene = new PIXI.Container();
gameState = defaultState;
/* User inputs */
// Mine
document.getElementById('consume').addEventListener('click', () => {consumeCubes(50)});
// Manual explode
let explodeOrigin = null
document.querySelector('canvas').addEventListener('click', e => {
if(!explodeOrigin){
explodeOrigin = {x: e.clientX, y: e.clientY};
}else{
explode(explodeOrigin, {x: e.clientX, y: e.clientY});
explodeOrigin = null;
}
});
window['explode'] = explode;
heightIndex = {};
cubesPerX = [];
// Todo fill with gameState.total cubes
cubes = [];
app.ticker.add(delta => gameLoop(delta));
app.stage.addChild(playScene);
}
/*
* UI
*/
function updateUI(){
document.getElementById('total').innerHTML = cubes.length;
}
/*
* Game logic
*/
// Add cube when user clicks
function mine(){
for(let i = 0; i < gameState.cubesPerDig; i++){
setTimeout(addCube, 5*i);
}
}
// Consume a number of cubes
function consumeCubes(nb){
let candidates = _.sampleSize(cubes.filter(c => !c.eDirX), Math.min(nb, cubes.length));
candidates = candidates.slice(0, nb);
candidates.map(c => {
dropCubes(c.x);
c.state = STATE.CONSUMING;
});
}
const STATE = {
LANDING: 0,
CONSUMING: 1,
EXPLODING: 2
}
// Add a cube
function addCube(){
let c = new cube(cubeSize);
let tres = dropSize / cubeSize / 2;
c.x = window.innerWidth / 2 + (_.random(-tres, tres) * cubeSize);
c.y = 0//-cubeSize;
c.speed = _.random(5,8);
cubes.push(c);
c.landed = !1;
c.state = STATE.LANDING;
if(!cubesPerX[c.x]) cubesPerX[c.x] = [];
if (!heightIndex[c.x]) heightIndex[c.x] = window.innerHeight - cubeSize;
cubesPerX[c.x].push(c);
playScene.addChild(c);
}
// Remove a cube
function removeCube(c){
let cube = cubes[c];
playScene.removeChild(cube);
cubes.splice(c,1);
}
// Delete the cube if offscreen
function boundings(c){
let cube = cubes[c];
if(cube.x < 0 || cube.x + cubeSize > window.innerWidth || cube.y < 0 || cube.y > window.innerHeight)
{
removeCube(c);
return true;
}
}
// explode some cubes
function explode(origin, dest){
if(dest.x < origin.x){
dest = [origin, origin = dest][0]; // swap
}
var candidates = cubes.filter(c => c.state != STATE.EXPLODING && c.x >= origin.x && c.x <= dest.x && c.y >= origin.y && c.y <= dest.y);
if(!candidates.length)
return;
for(let i = origin.x; i <= dest.x; i++){
dropCubes(i);
}
candidates.forEach(c => {
c.explodingSpeed = _.random(5,6);
c.eDirX = _.random(-1,1,1) * c.explodingSpeed * c.speed;
c.eDirY = _.random(-1,1,1) * c.explodingSpeed * c.speed;
c.state = STATE.EXPLODING;
});
}
// Drop cubes
function dropCubes(x){
heightIndex[x] = window.innerHeight - cubeSize;
if(cubesPerX[x] && cubesPerX[x].length)
cubesPerX[x].forEach(c => {
if(c.state == STATE.EXPLODING) return;
c.landed = false; c.state = STATE.LANDING;
});
}
/*
* Graphic display
*/
// Cube definition
function cube(size){
let graphic = new PIXI.Graphics();
graphic.beginFill(Math.random() * 0xFFFFFF);
graphic.drawRect(0, 0, size, size);
graphic.endFill();
return graphic;
}
// Init
setup();
})()
/* styles */
/* called by your view template */
* {
box-sizing: border-box;
}
body, html{
margin: 0;
padding: 0;
color:white;
}
#ui{
position: absolute;
z-index: 2;
top: 0;
width: 0;
left: 0;
bottom: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.8.1/pixi.min.js"></script>
<script>PIXI.utils.skipHello();</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
<!-- Click two points to make cubes explode -->
<div id="ui">
<button id="dig">
Dig 2k cubes
</button>
<button id="consume">
Consume 50 cubes
</button>
<p>
Total : <span id="total"></span>
</p>
</div>
Thanks
Using Sprites will always be faster than using Graphics (at least until v5 comes along!)
So I changed your cube creation function to
function cube(size) {
const sprite = new PIXI.Sprite(PIXI.Texture.WHITE);
sprite.tint = Math.random() * 0xFFFFFF;
sprite.width = sprite.height = size;
return sprite;
}
And that boosted the fps for myself

Phaser.js why overlap doesn't work?

I created a little scene on phaser.js
When the enemy shoots me, I have to die, but it don't work
And also, when I shoot the enemies they should die. I wrote an overlap function in code, but it doesn't work.
Here is a screenshot of the situation:
Why is this so, can you help me?
var playState = {
create: function()
{
this.enemiesArray = [];
game.physics.startSystem(Phaser.Physics.ARCADE);
this.spacebar;
this.spacebar = game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
this.escapeKey = game.input.keyboard.addKey(Phaser.Keyboard.ESC);
this.ship = game.add.sprite(game.width / 2, game.height / 2 + 300, 'ship');
this.ship.anchor.setTo(0.5, 0.5);
this.ship.scale.setTo(0.4, 0.4);
this.cursor = game.input.keyboard.createCursorKeys();
this.enemy = game.add.sprite(game.width / 2, game.height / 2 - 300, 'alien');
this.enemy.anchor.setTo(.5,.5);
this.enemy.scale.setTo(0.4,0.4);
game.time.events.loop(1000, this.spawnEnemy, this);
game.time.events.loop(750, this.spawnEnemyBullet, this);
game.physics.arcade.enable(this.ship, this.enemy, this.bullet, this.enemyBullet);
},
update: function()
{
if(this.cursor.left.isDown)
{
this.ship.x -= 7;
}
for(var a = 0; a < this.enemiesArray.length; a++)
{
this.enemiesArray[a].x -= 2;
}
if(this.escapeKey.isDown)
{
game.state.start('menu');
}
if(this.cursor.right.isDown)
{
this.ship.x += 7;
}
if(this.spacebar.isDown)
{
this.bullet = game.add.sprite(this.ship.x, this.ship.y, 'bullet');
this.bullet.anchor.setTo(0.5,0.5);
this.bullet.scale.setTo(0.2,0.2);
game.physics.arcade.enable(this.bullet);
this.bullet.body.velocity.y = -600;
if(!this.bullet.inWorld)
{
this.bullet.kill();
}
}
game.physics.arcade.overlap(this.enemyBullet,this.ship,this.gameOverOpenMenuScreen,null,this);
game.physics.arcade.overlap(this.bullet,this.enemy,this.killenemy,null,this);
},
killenemy: function()
{
this.enemy.kill();
},
gameOverOpenMenuScreen: function()
{
game.state.start('menu');
},
spawnEnemy: function()
{
this.enemy = game.add.sprite(Math.random()*game.width, game.height / 2 - 300, 'alien');
this.enemy.anchor.setTo(.5,.5);
this.enemy.scale.setTo(0.4,0.4);
this.enemiesArray.push(this.enemy);
},
spawnEnemyBullet: function()
{
for(var i = 0; i < this.enemiesArray.length; i++)
{
this.enemyBullet = game.add.sprite(this.enemiesArray[i].x, this.enemiesArray[i].y, 'bullet');
this.enemyBullet.anchor.setTo(0.5,0.5);
this.enemyBullet.scale.setTo(0.2,0.2);
this.enemyBullet.angle = 180;
game.physics.arcade.enable(this.enemyBullet);
this.enemyBullet.body.velocity.y = 600;
}
}
}
You should first group all bullets to one group:
const bulletsGroup = this.add.group();
(notice that this referees to the scene)
Then add each one of you bullets to the group:
bulletsGroup.add(<your game object>);
After those 2 steps you can set your overlap function. The function gets group/object for its first 2 arguments, and a callback to be invoked when an overlap occurs:
game.physics.arcade.overlap(this.ship, bulletGroup, () => console.log('boom!'));
You can use a group to create the bullets, but I think you should correct this first:
game.physics.arcade.enable([this.ship, this.enemy]);
Add this to your code:
create : function() {
...
//After creating the sprites
this.bulletPlayerTime = 0;
this.bulletsPlayer = game.add.group();
this.bulletsPlayer.enableBody = true;
this.bulletsPlayer.physicsBodyType = Phaser.Physics.ARCADE;
for (var i = 0; i < 20; i++)
{
var b = this.bulletsPlayer.create(0, 0, 'bullet');
b.name = 'bullet' + i;
b.exists = false;
b.visible = false;
b.checkWorldBounds = true;
b.events.onOutOfBounds.add(function(bullet) { bullet.kill(); }, this);
}
...
}
update : function() {
...
if (this.spacebar.isDown) {
this.firePlayer();
}
...
}
firePlayer : function() {
if (game.time.now > this.bulletPlayerTime) {
bullet = bulletsPlayer.getFirstExists(false);
if (bullet) {
bullet.reset(this.ship.x + 6, this.ship.y - 8);
bullet.body.velocity.y = -300;
this.bulletPlayerTime = game.time.now + 300;
}
}
}
Now you must do the same functionality but this time with the enemies ...
You can see more here

Phaser game getting slow [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
Fist time i am using phaser.io, i am repeating background and also loading other thing in update function but after few second later my game is slowing time . it look like background is not moving more. Please have a look of my code and help me in for sort out this problem. Or please give any idea to change background repeatedly without changing other thing.
I have some code indentation problem sorry for that but please try to manage and help me.
Game.js
var scoreTxt, score, speed, scoreTextValue, ques_label, ques_label_pizza, scoreTextKey, timerTextValue, timerTextKey, textStyle_Key, textStyle_Value, anscloud, astroid1, astroid2, astroid3, astroid4;
/*var gameType;*/ //Pizza or Noun
var bullets, quesTextValue, ansTextValue, sprite;
var fireRate = 100;
var nextFire = 0;
var xAxis = [];
var yAxis = [];
var tempQues = [];
var tempAns = [];
var result = [];
var answear = [];
var ques = [];
var astroidContains = [];
var astroidContainsText = []; //['right', 'wrong', 'wrong', 'wrong']
var astroid, spaceShip, quesbar, diamond, randomAnsPosition;
var s1Copy;
var cloudContains = []; //['noun', 'pronoun', 'pronoun']
var QbarContainsQue = [];
var ans,rightans;
var isAnswerCorrect = false;
var allowClick = false;
var spaceShipXAxis = 40, loader1Width = 85, loader2Width = 70;
var bar, loader1, loader2, timer, timerSprite, timerSpriteCount = 0;
var timerCounter = 45; //timer counter will be of 45 seconds.
//var timerCounter_ = 100; //timer counter will be of 45 seconds.
var questCounter = 0; //question counter no. of question played.
var maxQuest = 10;//max questions will be displayed is 10.
var diamondTextColor = "#8D4FA8";
var defTextColor = "#5BEFFE";
var ansTextColor = "#9E13DA";
var errTextColor = '#FF0000';
var corrTextColor = '#228B22';
var corr_ans_fst;
var corr_ans_sec;
var fun_bckg, randQues;
var wrong_ans;
var barre1_x = 150;
var barre1_y = 115;
var healthValue = 100;
var x_loader = 180;
var check =0;
var setAns = [];
var setOne = [['12+16=','28'], ['15+11=','26'], ['16+22=','38'], ['13+14=','27'], ['15+24=','39'], ['14+12=','26'], ['10+17=','27'], ['11+11=','22'],
['13+15=','28'], ['12+21=','33'], ['24+13=','37'], ['33+21=','54'], ['40+18=','58'], ['34+31=','65'], ['25+42=','67'], ['22+15=','37'],
['24+12=','36'], ['20+15=','35'], ['25+14=','39'], ['21+21=','42'], ['41+25=','66'], ['53+24=','77'], ['35+31=','66'], ['62+37=','99'],
['54+35=','89']];
var setTwo = [['15+18=','33'], ['17+17=','34'], ['13+19=','32'], ['18+14=','32'], ['15+27=','42'], ['18+17=','35'], ['27+29=','56'], ['23+28=','51'],
['36+37=','73'], ['45+25=','70'], ['46+45=','91'], ['38+57=','95'], ['49+43=','92'], ['37+53=','90'], ['48+33=','81']];
var Game = {
preload : function() {
// Load the needed image for this(play) game screen.
//load the menu screen
this.load.image('menu', './assets/images/menu.png');
// Here we load all the needed resources for the level.
// background image screen
this.load.image('playgame', './assets/images/back.png');
// globe image screen
this.load.image('playgame', './assets/images/back.png');
// win image screen
//this.load.image('win', './assets/images/win.png');
// spaceship image screen
this.load.image('spaceship', './assets/images/spaceship.png');
// Question bar image screen
this.load.image('quesbar', './assets/images/quesbar.png');
// Diamond image screen
this.load.image('diamond', './assets/images/diamond.png');
// Astroid image screen
this.load.image('astroid1', 'assets/images/asteroid1.png');
this.load.image('astroid2', 'assets/images/asteroid2.png');
this.load.image('astroid3', 'assets/images/asteroid3.png');
this.load.image('astroid4', 'assets/images/asteroid4.png');
// Loader image screen
this.load.image('loaderbck', 'assets/images/loaderbck.png');
this.load.image('loader1', 'assets/images/loader1.png');
this.load.image('loader2', 'assets/images/loader2.png');
//Load the bullet
this.load.image('bullet', 'assets/images/bullet.png');
},
create : function() {
// By setting up global variables in the create function, we initialise them on game start.
// We need them to be globally available so that the update function can alter them.
textStyle_Value = { font: "bold 20px Segoe UI", fill: defTextColor, align: "center" };
textStyleAns = { font: "bold 22px 'Comic Sans MS', 'Comic Sans'", fill: ansTextColor, wordWrap: true, wordWrapWidth: 10, align: "center"};
textStyleQues = { font: "bold 20px 'Comic Sans MS', 'Comic Sans'", fill: defTextColor, wordWrap: true, wordWrapWidth: 10, align: "center"};
sprite = game.add.sprite(310, 485, 'spaceship');
sprite.anchor.set(0.5);
// Loading backround image
this.playBackground();
this.playBackground1();
// Additional Sprites, like cloud
this.addSprites();
// Loading spaceship image
//this.spaceship();
// Loading questionbar image
this.questionbar();
// Call fun. for ques
this.comeQus();
// csll fun. for place astroid
// this.astroid();
// call fun. for Ans
this.generateQues();
this.generateAns();
// Loading Diamond image
this.diamond();
// Start timer
this.startTimer();
// Set timer.
this.setTimer();
this.initLoader();
},
update: function() {
// The update function is called constantly at a high rate (somewhere around 60fps),
// updating the game field every time - also destroying previous objects and creating new.
// Our bullet group
//bullets.destroy();
sprite.destroy();
bullets = game.add.group();
bullets.enableBody = true;
bullets.physicsBodyType = Phaser.Physics.ARCADE;
bullets.createMultiple(200, 'bullet', 100, false);
bullets.setAll('anchor.x',0);
bullets.setAll('anchor.y', 0.9);
bullets.setAll('outOfBoundsKill', true);
bullets.setAll('checkWorldBounds', true);
//Repeating background..
if(playgame != null && playgame.body.y > 600) {
playgame.destroy();
this.playBackground();
}
if(playgame1.body.y > 0) {
playgame1.destroy();
this.playBackground1();
this.initLoader();
}
if(astroid1 != undefined) astroid1.destroy();
if(astroid2 != undefined) astroid2.destroy();
if(astroid3 != undefined) astroid3.destroy();
if(astroid4 != undefined) astroid4.destroy();
this.addSprites();
//timerTextValue.text = "00:" + timerCounter;
this.initLoader();
//destroing old diamond obj and creating new while change background
//diamond.destroy();
this.diamond();
//destroing old questionbar obj and creating new while change background
quesbar.destroy();
this.questionbar();
//Call comeQus, comeAns for show ques and ans at every background change
// quesTextValue.destroy();
if(quesTextValue != undefined) quesTextValue.destroy();
this.comeQus();
//ansTextValue.destroy();
if(ansTextValue != undefined) ansTextValue.destroy();
this.comeAns();
if (game.input.activePointer.isDown) {
this.fire();
}
allowClick = true;
},
playBackground: function() {
// console.log("playBackground called");
playgame = this.add.sprite(0, 0, 'playgame', 5);
playgame.scale.set(1);
playgame.smoothed = false;
anim_playgame = playgame.animations.add('walk');
anim_playgame.play(10, true);
this.physics.enable(playgame, Phaser.Physics.ARCADE);
playgame.body.velocity.y = 50;
},
playBackground1: function() {
//console.log("playBackground1 called");
//Second background..
playgame1 = this.add.sprite(0, -600, 'playgame', 5);
playgame1.scale.set(1);
playgame1.smoothed = false;
anim_playgame1 = playgame1.animations.add('walk');
anim_playgame1.play(10, true);
this.physics.enable(playgame1, Phaser.Physics.ARCADE);
playgame1.body.velocity.y = 50;
},
questionbar: function() {
quesbar = game.add.image(10, 530, 'quesbar');
},
diamond: function() {
diamond = game.add.image(680, 20, 'diamond');
},
addSprites: function() {
// loading answer cloud
astroid1 = this.add.button(30, 90, 'astroid1', this.astroidClicked, this);
astroid2 = this.add.button(220, 30, 'astroid2', this.astroidClicked, this);
astroid3 = this.add.button(400, 40, 'astroid3', this.astroidClicked, this);
astroid4 = this.add.button(600, 90, 'astroid4', this.astroidClicked, this);
},
inCorrectAnswerHit: function(index) {
allowClick = false;
isAnswerCorrect = false;
//this.playFx('wrong_ans');
for(i=0; i<=3; i++) {
if(cloudContains[i] == "right") {
//cloudContainsText[i].fill = corrTextColor;
console.log("right ans hit");
break;
}
}
},
checkAnswer: function(index) {
// If clicked Ans is right so astroid will destroy.
if(astroidContainsText[index] == "wrong") {
//Here collization function will call
isAnswerCorrect = true;
}
// If clicked word is noun (correct answer) and obstacle is redbird or blackbird - the dude will slide.
else {
this.inCorrectAnswerHit(index);
}
},
generateQues: function(){
var que;
// Generating random questions from given list of ques - setOne.
s1Copy = setOne.slice();
//var result = [];
for (var i = 0; i < 3; i++) {result.push(s1Copy.splice(~~(Math.random()*s1Copy.length),1)[0]);}
s1Copy.push(...setTwo);
for (var i = 0; i < 7; i++) {result.push(s1Copy.splice(~~(Math.random()*s1Copy.length),1)[0]);}
result.toString();
for(var i = 0; i < result.length ; i++ ) {
que = result[i];
ques.push(que[0]);
ques.toString();
//console.log(ques);
answear.push(que[1]);
}
},
comeQus: function() {
quesTextValue = this.add.text(50,541, ques[0],textStyleQues);
this.generateQues();
//tempNoun = [];
},
generateAns: function() {
//Generate two digitd rendom no. and create an array of ans setAns[]
// Add digitd in array
for(var i = 0; i < 3 ; i++) {
var digit = Math.floor(Math.random() * 90 + 10);
//console.log(digit);
setAns.push(digit);
astroidContains[i] = "wrong";
}
console.log(astroidContains);
//console.log(answear);
setAns.push(answear[0]);
astroidContains[i] = "right";
console.log(astroidContains);
shuffle(setAns);
randomAnsPosition = [0, 1, 2, 3];
shuffle(randomAnsPosition);
},
comeAns: function() {
// x and y axis param for placing Answers text.
xAxis = [ 85, 255, 453, 675];
yAxis = [130, 48, 60, 120];
// console.log(setAns);
// Set Answers from above array of Ans - setAns.
for (var i = 0; i < setAns.length; i++) {
var ans = setAns[i];
//console.log(ans);
ansTextValue = this.add.text(xAxis[randomAnsPosition[i]], yAxis[randomAnsPosition[i]], ans, textStyleAns);
astroidContainsText[i] = ansTextValue;
//console.log(ansTextValue.text);
}
},
// Observing which cloud is clicked and checking answer accordingly.
astroidClicked: function() {
// alert("HEllo called");
if(!allowClick) {
return;
}
if(astroid1.game.input._x > 85 && astroid1.game.input._x < 130) {
console.log("cloud_1_Clicked, Clicked:" + astroidContains[0]);
this.checkAnswer(0);
}
else if(astroid2.game.input._x > 255 && astroid2.game.input._x < 48) {
//console.log("cloud_2_Clicked, Clicked:" + astroidContains[1]);
this.checkAnswer(1);
}
else if(astroid3.game.input._x > 453 && astroid3.game.input._x < 60) {
//console.log("cloud_3_Clicked, Clicked:" + astroidContains[2]);
this.checkAnswer(2);
}
else if(astroid4.game.input._x > 675 && astroid4.game.input._x < 120) {
//console.log("cloud_3_Clicked, Clicked:" + astroidContains[2]);
this.checkAnswer(3);
}
allowClick = false;
},
startTimer: function() {
// Create our Timer
timer = game.time.create(false);
// Set a TimerEvent to occur after 1 seconds
timer.loop(1000, this.updateCounter, this);
// Set a TimerEvent to occur after 1 seconds
// timer.loop(100, this.timerStripeChange, this);
// Start the timer running - this is important!
// It won't start automatically, allowing you to hook it to button events and the like.
timer.start();
},
gameOver: function() {
//Gameover screen
this.state.start('Game_Over', true, false);
},
initLoader: function() {
//*******Loader
check +=1;
var bmd = this.game.add.bitmapData(185, 30);
bmd.ctx.beginPath();
bmd.ctx.rect(0, 0, 185, 36);
bmd.ctx.fillStyle = '#00685e';
bmd.ctx.fill();
var bglife = this.game.add.sprite(100, 38, bmd);
bglife.anchor.set(0.5);
if(check != 0)
bmd = this.game.add.bitmapData(x_loader-4, 26);
else
bmd = this.game.add.bitmapData(x_loader, 26);
bmd.ctx.beginPath();
bmd.ctx.rect(0, 0, 180, 26);
if(x_loader <= 120 && x_loader > 60) {
bmd.ctx.fillStyle = "#FFFF00";
} else if(x_loader <= 60) {
bmd.ctx.fillStyle = "#EA0B1E";
} else {
bmd.ctx.fillStyle = '#00f910';
}
bmd.ctx.fill();
this.widthLife = new Phaser.Rectangle(0, 0, bmd.width, bmd.height);
this.totalLife = bmd.width;
//x_loader = ;
/*console.log(this.totalLife);
console.log(this.widthLife);*/
this.life = this.game.add.sprite(93 - bglife.width/2 + 10, 38, bmd);
this.life.anchor.y = 0.5;
this.life.cropEnabled = true;
this.life.crop(this.widthLife);
// this.game.time.events.loop(1450, this.cropLife, this);
},
updateCounter: function() {
if(timerCounter <= 0) {
this.gameOver();
return;
}
timerCounter--;
if(this.widthLife.width <= 0){
this.widthLife.width = this.totalLife;
}
else{
//this.game.add.tween(this.widthLife).to( { width: (x_loader - 4) }, 200, Phaser.Easing.Linear.None, true);
//console.log(this.widthLife.width);
this.widthLife.width = x_loader - 4;
x_loader = this.widthLife.width;
}
},
fire: function () {
if (game.time.now > nextFire && bullets.countDead() > 0)
{
nextFire = game.time.now + fireRate;
var bullet = bullets.getFirstDead();
bullet.reset(sprite.x - 80, sprite.y - 80);
game.physics.arcade.moveToPointer(bullet, 300);
}
}
}
/**
* Shuffles array in place.
* #param {Array} a items The array containing the items.
*/
function shuffle(a) {
var j, x, i;
for (i = a.length; i; i -= 1) {
j = Math.floor(Math.random() * i);
x = a[i - 1];
a[i - 1] = a[j];
a[j] = x;
}
}
As already noted it is a lot of code.
So far what I can see, is a memory leak in the update() function:
bullets = game.add.group();
bullets.enableBody = true;
bullets.physicsBodyType = Phaser.Physics.ARCADE;
bullets.createMultiple(200, 'bullet', 100, false);
bullets.setAll('anchor.x',0);
bullets.setAll('anchor.y', 0.9);
bullets.setAll('outOfBoundsKill', true);
bullets.setAll('checkWorldBounds', true);
With that you are constantly creating new bullets. Put that in the create() function and try again.

Apply drag and drop/attach feature to JSTreegraph using Jquery draggable

I am using JSTreegraph plugin to draw a tree structure.
But now I need a drag, drop and attach feature wherein I can drag any node of the tree and attach to any other node and subsequently all the children of the first node will be now the grand-children of the new node (to which it is attached).
As far as I know this plugin doesnt seem to have this feature. It simply draws the structure based on the data object passed to it.
The plugin basically assigns a class Node to all the nodes(divs) of the tree and another class NodeHover to a node on hover. No id is assigned to these divs.
So I tried using JQuery Draggable just to see if any of the node can be moved by doing this
$('.Node').draggable();
$('.NodeHover').draggable();
But it doesnt seem to work. So can anyone help me with this.
How can I get drag and attach functionality?
*EDIT:: Pardon me am not so great with using Fiddle, so am sharing a sample code here for your use:*
HTML file: which will draw the sample tree
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link href="css/JSTreeGraph.css" rel="stylesheet" type="text/css" />
<script src="js/JSTreeGraph.js" type="text/javascript"></script>
<script src="js/jquery-1.7.1.min.js" type="text/javascript"></script>
<script src="js/jquery.ui.position.js" type="text/javascript"></script>
<style type="text/css">
.Container {
position: absolute;
top: 100px;
left: 50px;
id: Container;
}
</style>
</head>
<body>
<div id="tree">
Ctrl+Click to Add Node
<br />
Double Click to Expand or Collapse
<br />
Shift+Click to Delete Node
<br />
<select id="dlLayout" onchange="ChangeLayout()">
<option value="Horizontal">
Horizontal
</option>
<option value="Vertical" selected>
Vertical
</option>
</select>
<div class="Container" id="dvTreeContainer"></div>
<script type="text/javascript">
var selectedNode;
// Root node
var rootNode = { Content: "Onida", Nodes:[] };
// First Level
rootNode.Nodes[0] = { Content: "Employee Code", navigationType: "0"};
rootNode.Nodes[1] = { Content: "Problem Area", navigationType: "1" };
// Second Level
rootNode.Nodes[1].Nodes = [{ Content : "ACC-HO", Collapsed: true /* This node renders collapsed */ },
{ Content : "ACC-SALES" },
{ Content : "BUSI. HEAD", /*This node looks different*/ ToolTip: "Click ME!" },
{ Content : "CEO"},
{ Content : "HO-ADMIN"},
{ Content : "HO-FACTORY"},
{ Content : "SALES"}];
// Third Level
rootNode.Nodes[1].Nodes[0].Nodes = [{ Content: "Billing" },
{ Content: "Credit Limit" },
{ Content: "Reconciliation" }];
rootNode.Nodes[1].Nodes[1].Nodes = [{ Content: "Billing" },
{ Content: "Others" }];
rootNode.Nodes[1].Nodes[2].Nodes = [{ Content: "AC" },
{ Content: "CTV" },
{ Content: "DVD" },
{ Content: "Washing Machine" }];
rootNode.Nodes[1].Nodes[6].Nodes = [{ Content: "Appointments" },
{ Content: "Resignations" },
{ Content: "Others" }];
// Draw the tree for the first time
RefreshTree();
function RefreshTree() {
DrawTree({ Container: document.getElementById("dvTreeContainer"),
RootNode: rootNode,
Layout: document.getElementById("dlLayout").value,
OnNodeClickFirefox: NodeClickFF,
OnNodeClickIE: NodeClickIE,
OnNodeDoubleClick: NodeDoubleClick
});
}
//function
function NodeClickFF(e) {
if (e.shiftKey){
// Delete Node
if (!this.Node.Collapsed) {
for(var index=0; index<this.Node.ParentNode.Nodes.length; index++) {
if(this.Node.ParentNode.Nodes[index].Content == this.Node.Content) {
this.Node.ParentNode.Nodes.splice(index, 1);
break;
}
}
RefreshTree();
}
// return false;
}
else if (e.ctrlKey) {
// Add new Child if Expanded
if (!this.Node.Collapsed) {
if (!this.Node.Nodes) this.Node.Nodes = new Array();
var newNodeIndex = this.Node.Nodes.length;
this.Node.Nodes[newNodeIndex] = new Object();
this.Node.Nodes[newNodeIndex].Content = this.Node.Content + "." + (newNodeIndex + 1);
RefreshTree();
}
// return false;
}
else{
fnNodeProperties(this.Node);
}
}
function NodeClickIE() {
if (typeof(event) == "undefined" && event.ctrlKey) {
// Add new Child if Expanded
if (!this.Node.Collapsed) {
if (!this.Node.Nodes) this.Node.Nodes = new Array();
var newNodeIndex = this.Node.Nodes.length;
this.Node.Nodes[newNodeIndex] = new Object();
this.Node.Nodes[newNodeIndex].Content = this.Node.Content + "." + (newNodeIndex + 1);
RefreshTree();
}
}
else if (typeof(event) == "undefined" && event.shiftKey) {
// Delete Node
if (!this.Node.Collapsed) {
for(var index=0; index<this.Node.ParentNode.Nodes.length; index++) {
if(this.Node.ParentNode.Nodes[index].Content == this.Node.Content) {
this.Node.ParentNode.Nodes.splice(index, 1);
break;
}
}
RefreshTree();
}
}
else{
fnNodeProperties(this.Node);
}
}
function NodeDoubleClick() {
if (this.Node.Nodes && this.Node.Nodes.length > 0) { // If has children
this.Node.Collapsed = !this.Node.Collapsed;
RefreshTree();
}
}
function ChangeLayout() {
RefreshTree();
}
</script>
</div>
</body>
</html>
JSTreeGraph JS file: plugin js file
function DrawTree(options) {
// Prepare Nodes
PrepareNode(options.RootNode);
// Calculate Boxes Positions
if (options.Layout == "Vertical") {
PerformLayoutV(options.RootNode);
} else {
PerformLayoutH(options.RootNode);
}
// Draw Boxes
options.Container.innerHTML = "";
DrawNode(options.RootNode, options.Container, options);
// Draw Lines
DrawLines(options.RootNode, options.Container);
}
function DrawLines(node, container) {
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // Has children and Is Expanded
for (var j = 0; j < node.Nodes.length; j++) {
if(node.ChildrenConnectorPoint.Layout=="Vertical")
DrawLineV(container, node.ChildrenConnectorPoint, node.Nodes[j].ParentConnectorPoint);
else
DrawLineH(container, node.ChildrenConnectorPoint, node.Nodes[j].ParentConnectorPoint);
// Children
DrawLines(node.Nodes[j], container);
}
}
}
function DrawLineH(container, startPoint, endPoint) {
var midY = (startPoint.Y + ((endPoint.Y - startPoint.Y) / 2)); // Half path between start en end Y point
// Start segment
DrawLineSegment(container, startPoint.X, startPoint.Y, startPoint.X, midY, 1);
// Intermidiate segment
var imsStartX = startPoint.X < endPoint.X ? startPoint.X : endPoint.X; // The lower value will be the starting point
var imsEndX = startPoint.X > endPoint.X ? startPoint.X : endPoint.X; // The higher value will be the ending point
DrawLineSegment(container, imsStartX, midY, imsEndX, midY, 1);
// End segment
DrawLineSegment(container, endPoint.X, midY, endPoint.X, endPoint.Y, 1);
}
function DrawLineV(container, startPoint, endPoint) {
var midX = (startPoint.X + ((endPoint.X - startPoint.X) / 2)); // Half path between start en end X point
// Start segment
DrawLineSegment(container, startPoint.X, startPoint.Y, midX, startPoint.Y, 1);
// Intermidiate segment
var imsStartY = startPoint.Y < endPoint.Y ? startPoint.Y : endPoint.Y; // The lower value will be the starting point
var imsEndY = startPoint.Y > endPoint.Y ? startPoint.Y : endPoint.Y; // The higher value will be the ending point
DrawLineSegment(container, midX, imsStartY, midX, imsEndY, 1);
// End segment
DrawLineSegment(container, midX, endPoint.Y, endPoint.X, endPoint.Y, 1);
}
function DrawLineSegment(container, startX, startY, endX, endY, lineWidth) {
var lineDiv = document.createElement("div");
lineDiv.style.top = startY + "px";
lineDiv.style.left = startX + "px";
if (startX == endX) { // Vertical Line
lineDiv.style.width = lineWidth + "px";
lineDiv.style.height = (endY - startY) + "px";
}
else{ // Horizontal Line
lineDiv.style.width = (endX - startX) + "px";
lineDiv.style.height = lineWidth + "px";
}
lineDiv.className = "NodeLine";
container.appendChild(lineDiv);
}
function DrawNode(node, container, options) {
var nodeDiv = document.createElement("div");
nodeDiv.style.top = node.Top + "px";
nodeDiv.style.left = node.Left + "px";
nodeDiv.style.width = node.Width + "px";
nodeDiv.style.height = node.Height + "px";
if (node.Collapsed) {
nodeDiv.className = "NodeCollapsed";
} else {
nodeDiv.className = "Node";
}
if (node.Class)
nodeDiv.className = node.Class;
if (node.Content)
nodeDiv.innerHTML = "<div class='NodeContent'>" + node.Content + "</div>";
if (node.ToolTip)
nodeDiv.setAttribute("title", node.ToolTip);
nodeDiv.Node = node;
// Events
if (options.OnNodeClickIE){
//alert('OnNodeClick');
nodeDiv.onclick = options.OnNodeClickIE;
}
// Events
if (options.OnNodeClickFirefox){
//alert('OnNodeClick');
nodeDiv.onmousedown = options.OnNodeClickFirefox;
}
//on right click
if (options.OnContextMenu){
//alert('OnContextMenu');
nodeDiv.oncontextmenu = options.OnContextMenu;
}
if (options.OnNodeDoubleClick)
nodeDiv.ondblclick = options.OnNodeDoubleClick;
nodeDiv.onmouseover = function () { // In
this.PrevClassName = this.className;
this.className = "NodeHover";
};
nodeDiv.onmouseout = function () { // Out
if (this.PrevClassName) {
this.className = this.PrevClassName;
this.PrevClassName = null;
}
};
container.appendChild(nodeDiv);
// Draw children
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // Has Children and is Expanded
for (var i = 0; i < node.Nodes.length; i++) {
DrawNode(node.Nodes[i], container, options);
}
}
}
function PerformLayoutV(node) {
var nodeHeight = 30;
var nodeWidth = 100;
var nodeMarginLeft = 40;
var nodeMarginTop = 20;
var nodeTop = 0; // defaultValue
// Before Layout this Node, Layout its children
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) {
for (var i = 0; i < node.Nodes.length; i++) {
PerformLayoutV(node.Nodes[i]);
}
}
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // If Has Children and Is Expanded
// My Top is in the center of my children
var childrenHeight = (node.Nodes[node.Nodes.length - 1].Top + node.Nodes[node.Nodes.length - 1].Height) - node.Nodes[0].Top;
nodeTop = (node.Nodes[0].Top + (childrenHeight / 2)) - (nodeHeight / 2);
// Is my top over my previous sibling?
// Move it to the bottom
if (node.LeftNode && ((node.LeftNode.Top + node.LeftNode.Height + nodeMarginTop) > nodeTop)) {
var newTop = node.LeftNode.Top + node.LeftNode.Height + nodeMarginTop;
var diff = newTop - nodeTop;
/// Move also my children
MoveBottom(node.Nodes, diff);
nodeTop = newTop;
}
} else {
// My top is next to my top sibling
if (node.LeftNode)
nodeTop = node.LeftNode.Top + node.LeftNode.Height + nodeMarginTop;
}
node.Top = nodeTop;
// The Left depends only on the level
node.Left = (nodeMarginLeft * (node.Level + 1)) + (nodeWidth * (node.Level + 1));
// Size is constant
node.Height = nodeHeight;
node.Width = nodeWidth;
// Calculate Connector Points
// Child: Where the lines get out from to connect this node with its children
var pointX = node.Left + nodeWidth;
var pointY = nodeTop + (nodeHeight/2);
node.ChildrenConnectorPoint = { X: pointX, Y: pointY, Layout: "Vertical" };
// Parent: Where the line that connect this node with its parent end
pointX = node.Left;
pointY = nodeTop + (nodeHeight/2);
node.ParentConnectorPoint = { X: pointX, Y: pointY, Layout: "Vertical" };
}
function PerformLayoutH(node) {
var nodeHeight = 30;
var nodeWidth = 100;
var nodeMarginLeft = 20;
var nodeMarginTop = 50;
var nodeLeft = 0; // defaultValue
// Before Layout this Node, Layout its children
if ((!node.Collapsed) && node.Nodes && node.Nodes.length>0) {
for (var i = 0; i < node.Nodes.length; i++) {
PerformLayoutH(node.Nodes[i]);
}
}
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // If Has Children and Is Expanded
// My left is in the center of my children
var childrenWidth = (node.Nodes[node.Nodes.length-1].Left + node.Nodes[node.Nodes.length-1].Width) - node.Nodes[0].Left;
nodeLeft = (node.Nodes[0].Left + (childrenWidth / 2)) - (nodeWidth / 2);
// Is my left over my left node?
// Move it to the right
if(node.LeftNode&&((node.LeftNode.Left+node.LeftNode.Width+nodeMarginLeft)>nodeLeft)) {
var newLeft = node.LeftNode.Left + node.LeftNode.Width + nodeMarginLeft;
var diff = newLeft - nodeLeft;
/// Move also my children
MoveRigth(node.Nodes, diff);
nodeLeft = newLeft;
}
} else {
// My left is next to my left sibling
if (node.LeftNode)
nodeLeft = node.LeftNode.Left + node.LeftNode.Width + nodeMarginLeft;
}
node.Left = nodeLeft;
// The top depends only on the level
node.Top = (nodeMarginTop * (node.Level + 1)) + (nodeHeight * (node.Level + 1));
// Size is constant
node.Height = nodeHeight;
node.Width = nodeWidth;
// Calculate Connector Points
// Child: Where the lines get out from to connect this node with its children
var pointX = nodeLeft + (nodeWidth / 2);
var pointY = node.Top + nodeHeight;
node.ChildrenConnectorPoint = { X: pointX, Y: pointY, Layout:"Horizontal" };
// Parent: Where the line that connect this node with its parent end
pointX = nodeLeft + (nodeWidth / 2);
pointY = node.Top;
node.ParentConnectorPoint = { X: pointX, Y: pointY, Layout: "Horizontal" };
}
function MoveRigth(nodes, distance) {
for (var i = 0; i < nodes.length; i++) {
nodes[i].Left += distance;
if (nodes[i].ParentConnectorPoint) nodes[i].ParentConnectorPoint.X += distance;
if (nodes[i].ChildrenConnectorPoint) nodes[i].ChildrenConnectorPoint.X += distance;
if (nodes[i].Nodes) {
MoveRigth(nodes[i].Nodes, distance);
}
}
}
function MoveBottom(nodes, distance) {
for (var i = 0; i < nodes.length; i++) {
nodes[i].Top += distance;
if (nodes[i].ParentConnectorPoint) nodes[i].ParentConnectorPoint.Y += distance;
if (nodes[i].ChildrenConnectorPoint) nodes[i].ChildrenConnectorPoint.Y += distance;
if (nodes[i].Nodes) {
MoveBottom(nodes[i].Nodes, distance);
}
}
}
function PrepareNode(node, level, parentNode, leftNode, rightLimits) {
if (level == undefined) level = 0;
if (parentNode == undefined) parentNode = null;
if (leftNode == undefined) leftNode = null;
if (rightLimits == undefined) rightLimits = new Array();
node.Level = level;
node.ParentNode = parentNode;
node.LeftNode = leftNode;
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // Has children and is expanded
for (var i = 0; i < node.Nodes.length; i++) {
var left = null;
if (i == 0 && rightLimits[level]!=undefined) left = rightLimits[level];
if (i > 0) left = node.Nodes[i - 1];
if (i == (node.Nodes.length-1)) rightLimits[level] = node.Nodes[i];
PrepareNode(node.Nodes[i], level + 1, node, left, rightLimits);
}
}
}
JSTreeGraph CSS file:
.NodeContent
{
font-family:Verdana;
font-size:small;
}
.Node
{
position:absolute;
background-color: #CCDAFF;
border: 1px solid #5280FF;
text-align:center;
vertical-align:middle;
cursor:pointer;
overflow:hidden;
}
.NodeHover
{
position:absolute;
background-color: #8FADFF;
border: 1px solid #5280FF;
text-align:center;
vertical-align:middle;
cursor:pointer;
overflow:hidden;
}
.NodeCollapsed
{
position:absolute;
background-color: #8FADFF;
border: 2px solid black;
text-align:center;
vertical-align:middle;
cursor:pointer;
overflow:hidden;
}
.NodeLine
{
background-color: #000066;
position:absolute;
overflow:hidden;
}
I supose that what you need is to stablish a mechanism to modify your tree logic structure on dragging and then reload the whole tree element. This way the plugin will render your new modified structure.
This is not an easy problem and you have not outlined the exact specifications.
When you move a node, should all child nodes move with it?
What happens to a node and its child elements when a node is dropped/attached?
The plugin that you are using works well for drawing static trees, but it is not written in such a way to allow for dynamically editing the tree.
I have to agree with #Bardo in that the easiest way to make this work for your needs is to understand how the tree has been manipulated. (Thankfully the plugin seems to provide an onNodeClick option which will allow you to understand which node you are intending to manipulate). Once the tree has been manipulated then it must be completely redrawn. (There doesn't appear to be a good way to partially draw a tree).

Categories

Resources