When I move camera the polygons disappear from canvas - phaser-framework

I am making isomorphic map maker (or am trying to so far) and am moving camera using the arrow keys. The problem I have is that when I go beyond the canvas range the map is cut.
It seems as it erases the parts that are not in canvas's range? I don't understand...
When I zoom out the objects are there, but when I reset the zoom the objects are not visible again.
Here is the code fragment I am using (I removed the debug mode and it is not even showing the polygons ;( ):
game.ts:
import 'phaser';
export default class Demo extends Phaser.Scene {
private cameraMoveSpeed: number = 6;
private tileSize: number = 64; // pixels
private cursors: Phaser.Types.Input.Keyboard.CursorKeys;
constructor() {
super('demo');
}
preload() {
this.load.image('grass', 'assets/grass.png');
}
create() {
// This will create a new object called "cursors", inside it will contain 4 objects: up, down, left and right.
// These are all Phaser.Key objects, so anything you can do with a Key object you can do with these.
this.cursors = this.input.keyboard.createCursorKeys();
this.cameras.main
.setBounds(-2048, -2048, 2048 * 2, 2048 * 2, true) // TODO what should this be?
.centerOn(0, 0)
.setZoom(1);
for (let i = 0; i < 20; i++) {
for (let j = 0; j < 20; j++) {
const x = j * this.tileSize;
const y = i * this.tileSize;
placetile.call(this, x, y); // Place tiles in isometric coordinates
}
}
// Zoom keys
this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A)
.on('down', function (key, event) {
console.log('Plus Clicked');
if (this.cameras.main.zoom < 2) {
this.cameras.main.zoom += 0.25;
}
}, this);
let minusKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S)
.on('down', function (key, event) {
console.log('Minus Clicked');
if (this.cameras.main.zoom > 0.25) {
this.cameras.main.zoom -= 0.25;
}
}, this);
this.input.on('wheel', function (pointer, gameObjects, deltaX, deltaY, deltaZ) {
if (deltaY < 0) {
console.log('Scrolled up (rotate left)');
} else {
console.log('Scrolled down (rotate right)');
}
});
}
update() {
// For example this checks if the up or down keys are pressed and moves the camera accordingly.
if (this.cursors.up.isDown) {
this.cameras.main.y += this.cameraMoveSpeed;
}
else if (this.cursors.down.isDown) {
this.cameras.main.y -= this.cameraMoveSpeed;
}
if (this.cursors.left.isDown) {
this.cameras.main.x += this.cameraMoveSpeed;
}
else if (this.cursors.right.isDown) {
this.cameras.main.x -= this.cameraMoveSpeed;
}
}
}
function placetile(x, y) {
const isoPoint = cartesianToIsometric(new Phaser.Geom.Point(x, y));
console.log(isoPoint, this.tileSize);
const tile = this.add.polygon(isoPoint.x, isoPoint.y, [
this.tileSize, 0,
0, this.tileSize / 2,
0 + this.tileSize, this.tileSize,
0 + this.tileSize * 2, this.tileSize / 2
], 0xeeeeee)
// const tile = this.add.sprite(isoPoint.x, isoPoint.y, 'grass')
.setOrigin(0.5, 0.5)
// .setDepth(y)
.setInteractive(new Phaser.Geom.Polygon([
this.tileSize, 0,
0, this.tileSize / 2,
0 + this.tileSize, this.tileSize,
0 + this.tileSize * 2, this.tileSize / 2,
]), Phaser.Geom.Polygon.Contains)
.on('pointerover', function (event) {
this.setStrokeStyle(4, 0x7878ff, 0.5);
})
.on('pointerout', function (event) {
this.setStrokeStyle(0);
});
console.log(tile);
// this.input.enableDebug(tile, 0xff00ff);
}
const config: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO,
backgroundColor: '#eeeeee',
scale: {
width: 1280,
height: 1024,
parent: 'content'
// mode: Phaser.Scale.RESIZE ????
},
scene: Demo,
// physics: {
// default: 'arcade',
// arcade: {
// debug: true
// }
// },
};
const game = new Phaser.Game(config);
function cartesianToIsometric(cartPt) {
return new Phaser.Geom.Point(
cartPt.x - cartPt.y,
(cartPt.x + cartPt.y) / 2
);
}
function isometricToCartesian(isoPt) {
var tempPt = new Phaser.Geom.Point();
tempPt.x = (2 * isoPt.y + isoPt.x) / 2;
tempPt.y = (2 * isoPt.y - isoPt.x) / 2;
return (tempPt);
}
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Phaser Demo</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
<style>
body {
margin: 0;
padding: 0;
}
canvas {
width: 100%;
height: auto;
}
</style>
</head>
<body>
<div class="container">
<div id="content"></div>
</div>
<script src="game.js"></script>
</body>

You're using this.camera.main.x and this.camera.main.y. Those properties seem as though they don't update rendering for previously offscreen textures. I linked a copy of your code that works when using this.camera.main.scrollX and this.camera.main.scrollY. Those are the correct properties to use for scrolling the camera.
Here's a link to the docs that explicitly states scrollX/scrollY as the properties to use for scrolling:
https://photonstorm.github.io/phaser3-docs/Phaser.Cameras.Scene2D.Camera.html#scrollX__anchor
Copied your code here with those changes to show working:
https://stackblitz.com/edit/phaser-camera-main-scrollx-not-x

Related

How do I add an object next to a moving object in Phaser?

In the snippet below, I place two red squares next to each other. You can see there is no gap between them.
If I try to do the same thing with moving objects, there is a slight gap. What's the right way to do this?
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.min.js"></script>
</head>
<body>
<script>
var config = {
type: Phaser.AUTO,
width: 800,
height: 600,
physics: { default: 'arcade' },
scene: { create: create, update: update },
};
var game = new Phaser.Game(config);
var red, red2, yellow, yellow2;
const velocity = -300;
function create() {
red = this.add.rectangle(400, 200, 100, 100, 0xff0000).setOrigin(0, 0);
red2 = this.add.rectangle(red.x + 100, 250, 100, 100, 0xff6666).setOrigin(0, 0);
yellow = this.add.rectangle(400, 400, 100, 100, 0xffff00).setOrigin(0, 0);
this.physics.add.existing(yellow);
yellow.body.velocity.x = velocity;
}
function update() {
if (yellow.x < 200 && !yellow2) {
yellow2 = this.add.rectangle(yellow.x + 100, 450, 100, 100, 0xffff66).setOrigin(0, 0);
this.physics.add.existing(yellow2);
yellow2.body.velocity.x = velocity;
}
if (yellow.x < -200) {
this.scene.restart();
yellow2 = undefined;
}
}
</script>
</body>
</html>
I think the solution is: Use the position of the physics body by changing yellow.x + 100 to yellow.body.x + 100.
(Or maybe yellow.body.position.x + 100? Not sure what the difference is, or if body.x is just an alias for body.position.x.)
Related: https://phaser.discourse.group/t/lag-in-dom-sprite-position-updates/798
A easy way out is to use the Phaser function: Phaser.Display.Align.To.RightCenter and the event: postupdate.
You then just have to calculate the offset between the objects and add pass it into the function. ( documentation to the Align function). this is based on this solution: phaser forum
canvas{
transform: translate(-40%, -40%) scale(.3);
}
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.min.js"></script>
</head>
<body style="overflow:hidden">
<script>
var config = {
type: Phaser.AUTO,
width: 800,
height: 600,
physics: { default: 'arcade' },
scene: { create: create, update: update },
};
var game = new Phaser.Game(config);
var red, red2, yellow, yellow2;
const velocity = -300;
var yOffSet = 0;
function create() {
red = this.add.rectangle(400, 200, 100, 100, 0xff0000).setOrigin(0, 0);
red2 = this.add.rectangle(red.x + 100, 250, 100, 100, 0xff6666).setOrigin(0, 0);
yellow = this.add.rectangle(400, 400, 100, 100, 0xffff00).setOrigin(0, 0);
this.physics.add.existing(yellow);
yellow.body.velocity.x = velocity;
this.events.on("postupdate", function () {
if (yellow && yellow2) {
Phaser.Display.Align.To.RightCenter(yellow2, yellow, 0, yOffSet);
}
});
}
function update(time, delta) {
if (yellow.x < 200 && !yellow2) {
yellow2 = this.add.rectangle(yellow.x + 100, 450, 100, 100, 0xffff66).setOrigin(0, 0);
this.physics.add.existing(yellow2);
yellow2.body.velocity.x = velocity;
yOffSet = yellow2.y - yellow.y
}
if (yellow.x < -200) {
this.scene.restart();
yellow2 = undefined
}
}
</script>
</body>
</html>
Alternativly you could, calculate the x position more exact, but it is no easy since, the update function is not called in the same time interval. This could cause minor overlap or splitting.
// deltaTime being the the second paramenter of the update function
yellow2 = this.add.rectangle(yellow.x + 100 + (velocity * deltaTime), 450, 100, 100, 0xff0000).setOrigin(0, 0);
Or even easier, you could simply set the x in the postUpdate event (without Align)
this.events.on("postupdate", function () {
if ( yellow2) {
yellow2.x =yellow.x + 100;
}
});

Phaser3 multimple scenes issus. "Uncaught ReferenceError: loadScene is not defined at config.js:17"

I want to make a game in Phaser3 with multiple scenes. When I try to run the code with "cordova run browser" it renders a grey screen and in the code inspect console I get the error: "Uncaught ReferenceError: loadScene is not defined at config.js:19".
//I linked all the files in the index.html
<script type="text/javascript" src="./js/config.js"></script>
<script type="text/javascript" src="./js/helpers.js"></script>
<script type="text/javascript" src="./js/loadScene.js"></script>
<script type="text/javascript" src="./js/mainMenu.js"></script>
<script type="text/javascript" src="./js/gamePlay.js"></script>
<script type="text/javascript" src="./js/gameOver.js"></script>```
// set the configuration file config.js
const gameState = {
score: 0
};
const config = {
type: Phaser.AUTO,
width: 800,
height: 1368,
physics: {
default: 'arcade',
arcade: { debug: true }
},
scene: [loadScene, mainMenu, gamePlay, gameOver] //**here a get the error**
};
const game = new Phaser.Game(config);
// loadScene.js is one of the scenes
class loadScene extends Phaser.Scene {
constructor() { super({ key: 'loadScene' }); }
preload() {
this.load.image('background', '../img/BG/bgVstretch.png');
}
create() {
window.addEventListener('resize', resize);
resize();
this.add.image(0, 0, 'background').setOrigin(0);
this.add.text(100, 100, "Loading Scene", { font: "20px Impact" });
this.input.on('pointerdown', () => {
this.scene.stop('loadScene');
this.scene.start('mainMenu');
});
}
}
// mainMenu.js gamePlay.js gameOver.js....have the same structure as loadScene.js
// helpers.js contains the functions that resizes the game according to the screen.
function resize() {
var canvas = gameState.game.canvas,
width = window.innerWidth,
height = window.innerHeight;
var wratio = width / height,
ratio = canvas.width / canvas.height;`
`if (wratio < ratio) {
canvas.style.width = width + "px";
canvas.style.height = (width / ratio) + "px";
} else {
canvas.style.width = (height * ratio) + "px";
canvas.style.height = height + "px";
}
}```
The game does not render. For now I just wanted it to switch from one scene to another on pointerdown.
if you would like to switch scenes for me the following code worked
`this.scene.switch('whatever the key is')`
I wrote this code after I created a rectangle, this code makes the rectangle interactive and when you click on it it takes you to the next level.
`advanceButton.setInteractive();
advanceButton.on('pointerup', ()=>{
this.scene.switch('level2')
})
});`
as for why your code isn't rendering remove the semicolon after you finish the key

p5.js and node.js sync the position of x and y for little blobs

I'm currently making a agar.io like program for my school project using p5.js and node.js for the networking. However I'm having a problem setting all the little blobs in one locations for multiplayer mode because I wrote the program of setting the little blobs on a local javascript(circle.js). I tried to transfer the functions of the local javascript to the server.js(node.js) but when i call it, it only hangs up. This is the screenshot of the directory.
Here is the code of server.js
var express = require('express');
var app = express();
var server = app.listen(3000);
app.use(express.static('public'));
console.log("Running");
var socket = require('socket.io');
var io = socket(server);
io.sockets.on('connection', newConnection);
function newConnection(socket){
console.log('new connection' + socket.id);
}
function asd(){
fill(255);
ellipse(200, 200, 100 * 2, 100 * 2);
}
Here is the code of the index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>agar.io</title>
<script src="libraries/p5.js" type="text/javascript"></script>
<script src="libraries/p5.dom.js" type="text/javascript"></script>
<script src="libraries/p5.sound.js" type="text/javascript"></script>
<script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
<script src="sketch.js" type="text/javascript"></script>
<script src="circle.js" type="text/javascript"></script>
<script src="C:/Users/hp/Desktop/p5.js/Project/agario/server.js" type="text/javascript"></script>
<style> body {padding: 0; margin: 0;} canvas {vertical-align: top;} </style>
</head>
<body>
</body>
</html>
Here is the code of Circle.js
function Circle(positionX, positionY, radius) {
this.position = createVector(positionX, positionY);
this.radius = radius;
this.velocity = createVector(0, 0);
this.show = function() {
fill(255);
ellipse(this.position.x, this.position.y, this.radius * 2, this.radius * 2);
}
this.update = function() {
var newVelocity;
velocity = createVector(mouseX - width / 2, mouseY - height / 2);
newVelocity = createVector(mouseX - width / 2, mouseY - height / 2);
newVelocity.setMag(3);
this.velocity.lerp(newVelocity, 0.2);
this.position.add(this.velocity);
}
this.eat = function(other) {
var distance = p5.Vector.dist(this.position, other.position);
if (distance < this.radius + other.radius) {
var area = Math.PI * Math.pow(this.radius, 2) + Math.PI * Math.pow(other.radius, 2);
this.radius = Math.sqrt(area / Math.PI);
return true;
} else {
return false;
}
}
}
Here is the code of sketch.js
var circle;
var circles = [];
var zoom = 1;
var newZoom;
var socket;
function setup() {
socket = io.connect('http://localhost:3000');
createCanvas(1366, 666);
circle = new Circle(0, 0, 64);
for (var x = 0; x < 410; x++) {
circles[x] = new Circle(random(-width, width * 4), random(-height, height * 4), 20);
}
}
function draw() {
background(60);
translate(width / 2, height / 2);
newZoom = (64 / circle.radius*1.5);
zoom = lerp(zoom, newZoom, 0.1);
scale(zoom);
translate(-circle.position.x, -circle.position.y);
for (var x = circles.length - 1; x >= 0; x--) {
if (circle.eat(circles[x])) {
circles.splice(x, 1);
}
}
circle.show();
circle.update();
for (var x = 0; x < circles.length; x++) {
circles[x].show();
}
asd();
}
As you can see, i tried to call a function on node.js just to try if it is valid to get an information from server.js to have a similar counts and positions of little blobs, my question is how I can make a server that gives an x and y position for the little blobs?
socket.on('mouse',
function(data) {
// Data comes in as whatever was sent, including objects
console.log("Received: 'mouse' " + data.x + " " + data.y);
// Send it to all other clients
socket.broadcast.emit('mouse', data);
// This is a way to send to everyone including sender
// io.sockets.emit('message', "this goes to everyone");
}
);

Understanding state diagram editor

From the blog: http://bl.ocks.org/lgersman/5370827
I want to understand about how connection line between circles are implemented. I tried to go through it but it just went over my head. There's not much documentation about the example I found on blog. I guess other new users like me would be facing the same challenge.
If any one can explain the below sample code, that would be great!
Here's the code I minified for my requirement:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>d3.js selection frame example</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.js"></script>
<link rel="stylesheet" href="<%=request.getContextPath()%>/app.css" />
<script>
window.onload = function ()
{
var radius = 40;
window.states = [
{x: 43, y: 67, label: "first", transitions: []},
{x: 340, y: 150, label: "second", transitions: []},
{x: 200, y: 250, label: "third", transitions: []}
];
window.svg = d3.select('body')
.append("svg")
.attr("width", "960px")
.attr("height", "500px");
// define arrow markers for graph links
svg.append('svg:defs').append('svg:marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 4)
.attr('markerWidth', 8)
.attr('markerHeight', 8)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('class', 'end-arrow')
;
// line displayed when dragging new nodes
var drag_line = svg.append('svg:path')
.attr({
'class': 'dragline hidden',
'd': 'M0,0L0,0'
})
;
**// NEED EXPLANATION FROM HERE**
var gTransitions = svg.append('g').selectAll("path.transition");
var gStates = svg.append("g").selectAll("g.state");
var transitions = function () {
return states.reduce(function (initial, state) {
return initial.concat(
state.transitions.map(function (transition) {
return {source: state, transition: transition};
})
);
}, []);
};
var transformTransitionEndpoints = function (d, i) {
var endPoints = d.endPoints();
var point = [
d.type == 'start' ? endPoints[0].x : endPoints[1].x,
d.type == 'start' ? endPoints[0].y : endPoints[1].y
];
return "translate(" + point + ")";
}
var transformTransitionPoints = function (d, i) {
return "translate(" + [d.x, d.y] + ")";
}
var computeTransitionPath = (function () {
var line = d3.svg.line()
.x(function (d, i) {
return d.x;
})
.y(function (d, i) {
return d.y;
})
.interpolate("cardinal");
return function (d) {
var source = d.source,
target = d.transition.points.length && d.transition.points[0] || d.transition.target,
deltaX = target.x - source.x,
deltaY = target.y - source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist,
sourcePadding = radius + 4, //d.left ? 17 : 12,
sourceX = source.x + (sourcePadding * normX),
sourceY = source.y + (sourcePadding * normY);
source = d.transition.points.length && d.transition.points[ d.transition.points.length - 1] || d.source;
target = d.transition.target;
deltaX = target.x - source.x;
deltaY = target.y - source.y;
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
normX = deltaX / dist;
normY = deltaY / dist;
targetPadding = radius + 8;//d.right ? 17 : 12,
targetX = target.x - (targetPadding * normX);
targetY = target.y - (targetPadding * normY);
var points =
[{x: sourceX, y: sourceY}].concat(
d.transition.points,
[{x: targetX, y: targetY}]
)
;
var l = line(points);
return l;
};
})();
var dragPoint = d3.behavior.drag()
.on("drag", function (d, i) {
console.log("transitionmidpoint drag");
var gTransitionPoint = d3.select(this);
gTransitionPoint.attr("transform", function (d, i) {
d.x += d3.event.dx;
d.y += d3.event.dy;
return "translate(" + [d.x, d.y] + ")"
});
// refresh transition path
gTransitions.selectAll("path").attr('d', computeTransitionPath);
// refresh transition endpoints
gTransitions.selectAll("circle.endpoint").attr({
transform: transformTransitionEndpoints
});
// refresh transition points
gTransitions.selectAll("circle.point").attr({
transform: transformTransitionPoints
});
d3.event.sourceEvent.stopPropagation();
});
var renderTransitionMidPoints = function (gTransition) {
gTransition.each(function (transition) {
var transitionPoints = d3.select(this).selectAll('circle.point').data(transition.transition.points, function (d) {
return transition.transition.points.indexOf(d);
});
transitionPoints.enter().append("circle")
.attr({
'class': 'point',
r: 4,
transform: transformTransitionPoints
})
.call(dragPoint);
transitionPoints.exit().remove();
});
};
var renderTransitionPoints = function (gTransition) {
gTransition.each(function (d) {
var endPoints = function () {
var source = d.source,
target = d.transition.points.length && d.transition.points[0] || d.transition.target,
deltaX = target.x - source.x,
deltaY = target.y - source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist,
sourceX = source.x + (radius * normX),
sourceY = source.y + (radius * normY);
source = d.transition.points.length && d.transition.points[ d.transition.points.length - 1] || d.source;
target = d.transition.target;
deltaX = target.x - source.x;
deltaY = target.y - source.y;
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
normX = deltaX / dist;
normY = deltaY / dist;
targetPadding = radius + 8;//d.right ? 17 : 12,
targetX = target.x - (radius * normX);
targetY = target.y - (radius * normY);
return [{x: sourceX, y: sourceY}, {x: targetX, y: targetY}];
};
var transitionEndpoints = d3.select(this).selectAll('circle.endpoint').data([
{endPoints: endPoints, type: 'start'},
{endPoints: endPoints, type: 'end'}
]);
transitionEndpoints.enter().append("circle")
.attr({
'class': function (d) {
return 'endpoint ' + d.type;
},
r: 4,
transform: transformTransitionEndpoints
})
;
transitionEndpoints.exit().remove();
});
};
var renderTransitions = function () {
gTransition = gTransitions.enter().append('g')
.attr({
'class': 'transition'
})
gTransition.append('path')
.attr({
d: computeTransitionPath,
class: 'background'
})
.on({
dblclick: function (d, i) {
gTransition = d3.select(d3.event.target.parentElement);
if (d3.event.ctrlKey) {
var p = d3.mouse(this);
gTransition.classed('selected', true);
d.transition.points.push({x: p[0], y: p[1]});
renderTransitionMidPoints(gTransition, d);
gTransition.selectAll('path').attr({
d: computeTransitionPath
});
} else {
var gTransition = d3.select(d3.event.target.parentElement),
transition = gTransition.datum(),
index = transition.source.transitions.indexOf(transition.transition);
transition.source.transitions.splice(index, 1)
gTransition.remove();
d3.event.stopPropagation();
}
}
});
gTransition.append('path')
.attr({
d: computeTransitionPath,
class: 'foreground'
});
renderTransitionPoints(gTransition);
renderTransitionMidPoints(gTransition);
gTransitions.exit().remove();
};
var renderStates = function () {
var gState = gStates.enter()
.append("g")
.attr({
"transform": function (d) {
return "translate(" + [d.x, d.y] + ")";
},
'class': 'state'
})
.call(drag);
gState.append("circle")
.attr({
r: radius + 4,
class: 'outer'
})
.on({
mousedown: function (d) {
console.log("state circle outer mousedown");
startState = d, endState = undefined;
// reposition drag line
drag_line
.style('marker-end', 'url(#end-arrow)')
.classed('hidden', false)
.attr('d', 'M' + d.x + ',' + d.y + 'L' + d.x + ',' + d.y);
// force element to be an top
this.parentNode.parentNode.appendChild(this.parentNode);
//d3.event.stopPropagation();
},
mouseover: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", true);
},
mouseout: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", false);
//$( this).popover( "hide");
}
});
gState.append("circle")
.attr({
r: radius,
class: 'inner'
})
.on({
mouseover: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", true);
},
mouseout: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", false);
},
});
};
var startState, endState;
var drag = d3.behavior.drag()
.on("drag", function (d, i) {
console.log("drag");
if (startState) {
return;
}
var selection = d3.selectAll('.selected');
// if dragged state is not in current selection
// mark it selected and deselect all others
if (selection[0].indexOf(this) == -1) {
selection.classed("selected", false);
selection = d3.select(this);
selection.classed("selected", true);
}
// move states
selection.attr("transform", function (d, i) {
d.x += d3.event.dx;
d.y += d3.event.dy;
return "translate(" + [d.x, d.y] + ")"
});
// move transistion points of each transition
// where transition target is also in selection
var selectedStates = d3.selectAll('g.state.selected').data();
var affectedTransitions = selectedStates.reduce(function (array, state) {
return array.concat(state.transitions);
}, [])
.filter(function (transition) {
return selectedStates.indexOf(transition.target) != -1;
});
affectedTransitions.forEach(function (transition) {
for (var i = transition.points.length - 1; i >= 0; i--) {
var point = transition.points[i];
point.x += d3.event.dx;
point.y += d3.event.dy;
}
});
// reappend dragged element as last
// so that its stays on top
selection.each(function () {
this.parentNode.appendChild(this);
});
// refresh transition path
gTransitions.selectAll("path").attr('d', computeTransitionPath);
// refresh transition endpoints
gTransitions.selectAll("circle.endpoint").attr({
transform: transformTransitionEndpoints
});
// refresh transition points
gTransitions.selectAll("circle.point").attr({
transform: transformTransitionPoints
});
d3.event.sourceEvent.stopPropagation();
})
.on("dragend", function (d) {
console.log("dragend");
// needed by FF
drag_line.classed('hidden', true)
.style('marker-end', '');
if (startState && endState) {
startState.transitions.push({label: "transition label 1", points: [], target: endState});
update();
}
startState = undefined;
d3.event.sourceEvent.stopPropagation();
});
svg.on({
mousedown: function () {
console.log("mousedown", d3.event.target);
if (d3.event.target.tagName == 'svg') {
if (!d3.event.ctrlKey) {
d3.selectAll('g.selected').classed("selected", false);
}
var p = d3.mouse(this);
}
},
mousemove: function () {
var p = d3.mouse(this);
// update drag line
drag_line.attr('d', 'M' + startState.x + ',' + startState.y + 'L' + p[0] + ',' + p[1]);
var state = d3.select('g.state .inner.hover');
endState = (!state.empty() && state.data()[0]) || undefined;
},
mouseup: function () {
console.log("mouseup");
// remove temporary selection marker class
d3.selectAll('g.state.selection').classed("selection", false);
},
mouseout: function ()
{
if (!d3.event.relatedTarget || d3.event.relatedTarget.tagName == 'HTML') {
// remove temporary selection marker class
d3.selectAll('g.state.selection').classed("selection", false);
}
}
});
update();
function update() {
gStates = gStates.data(states, function (d) {
return states.indexOf(d);
});
renderStates();
var _transitions = transitions();
gTransitions = gTransitions.data(_transitions, function (d) {
return _transitions.indexOf(d);
});
renderTransitions();
}
;
};
</script>
</head>
<body>
</body>
</html>
Css file:
rect.selection {
stroke : gray;
stroke-dasharray: 2px;
stroke-opacity : 0.5;
fill : transparent;
}
g.state circle {
stroke : gray;
}
g.state circle.inner {
fill : white;
transition : fill 0.5s;
cursor : move;
}
g.state circle.inner.hover,
g.state circle.outer.hover {
fill : aliceblue;
}
g.state circle.outer.hover {
stroke-width : 1px;
}
g.state circle.outer {
stroke-width : 0px;
stroke-dasharray: 2px;
stroke-color : gray;
fill : transparent;
transition : all 0.5s;
cursor : pointer;
}
g.state.selected circle.outer {
stroke-width : 1px;
}
g.state text {
font : 12px sans-serif;
font-weight : bold;
pointer-events : none;
}
g.transition path,
path.dragline {
fill : none;
stroke : gray;
stroke-width: 1px;
}
g.transition path.foreground {
marker-end : url(#end-arrow);
}
g.transition.hover path.background {
stroke-dasharray: none;
stroke : aliceblue;
stroke-opacity : 1.0;
transition : all 0.5s;
}
g.transition path.background {
stroke-dasharray: none;
stroke-width: 8px;
stroke : transparent;
}
g.transition.selected path.foreground {
stroke-dasharray: 2px;
stroke-color : gray;
}
g.transition path {
cursor : default;
}
.end-arrow {
fill : gray;
stroke-width : 1px;
}
g.transition circle.endpoint {
display : none;
fill : none;
cursor : pointer;
stroke : gray;
stroke-dasharray: 2px;
}
g.transition circle.point {
display : none;
fill : aliceblue;
cursor : move;
stroke : gray;
}
g.transition.selected circle.endpoint,
g.transition.selected circle.point {
display : inline;
transition : all 0.5s;
}
g.transition:not( .selected).hover *,
path.dragline {
display : inline;
}
g.transition:not( .selected).hover {
transition : all 0.5s;
}
path.dragline {
pointer-events: none;
stroke-opacity : 0.5;
stroke-dasharray: 2px;
}
path.dragline.hidden {
stroke-width: 0;
}
/* disable text selection */
svg *::selection {
background : transparent;
}
svg *::-moz-selection {
background:transparent;
}
svg *::-webkit-selection {
background:transparent;
}
I understand the basics about d3 such as appending new circle and drag behaviors, but mainly the transitions part used to draw and connect lines to circle is holding me back.
There is quite a lot in there and actually, and looking at it it's not actually using typical css transitions as you might expect. I'll summarize the interesting parts and expand if you need. The interesting section is the following:
var dragPoint = d3.behavior.drag()
.on("drag", function( d, i) {
console.log( "transitionmidpoint drag");
var gTransitionPoint = d3.select( this);
gTransitionPoint.attr( "transform", function( d, i) {
d.x += d3.event.dx;
d.y += d3.event.dy;
return "translate(" + [ d.x,d.y ] + ")"
});
// refresh transition path
gTransitions.selectAll( "path").attr( 'd', computeTransitionPath);
// refresh transition endpoints
gTransitions.selectAll( "circle.endpoint").attr({
transform : transformTransitionEndpoints
});
// refresh transition points
gTransitions.selectAll( "circle.point").attr({
transform : transformTransitionPoints
});
d3.event.sourceEvent.stopPropagation();
});
This is where all the hard work is taking place. This code basically says, whenever the drag event occurs (e.g. you move a circle) execute the code within this function.
You can see this splits into sections:
Move the point that was clicked by a d.x and d.y which is the amount dragged from the previous event. This is done using the translate transform.
Change the path, this is done by updating the d parameter, which is how you specify the shape of an SVG path. The code that calculates the path is within the comuteTransitionPath function.
Update the circle.endpoint - This is a hidden point on the line
Update the circle.point - These are points on the line that control the curve, they are hidden by default.

Node JS html Game

<!doctype html>
<html>
<head>
<script src="http://localhost:8000/socket.io/socket.io.js"></script>
<script src="http://code.jquery.com/jquery-1.6.2.min.js"></script>
<script>
var name='';
var socket = io.connect('http://localhost:8000');
$(document).ready(function() {
//var app = require('http').createServer(handler),
//io = require('socket.io').listen(app);
//app.listen(8000);
//var url = 'http://localhost:8000';
//var socket = io.connect(url);
//socket.connect();
//socket.on('movement', function() {socket.send();
//console.log('Connected!');});
while (name == '') { name = prompt("What's your name?",""); }
var left =5;
var top = 5;
var width =20;
var height =20;
var rcolor= get_random_color();
var ctx = $('#cgame')[0].getContext("2d");
ctx.fillStyle = rcolor;
ctx.fillRect(left, top, width, height);
ctx.lineWidth = 10;
$(document).keydown(onkeydown);
socket.emit('movement', function onkeydown(left,top, width, height)
{
var kycode;
if (evt!= null)
{
kycode = evt.keyCode;
ctx = $('#cgame')[0].getContext("2d");
switch(kycode)
{
case 37: //left
if(left >> ctx.left){
call: clear();
left--;
call:draw();
//alert("Hi left");
break;
}
case 38: //up
if(top >> ctx.top)
{
call: clear();
top--;
call:draw();
//alert("Hi Up");
break;
}
case 39://right
if((left+width) << (ctx.width+ctx.left) )
{
call: clear();
left++;
call:draw();
//alert("Hi right");
break;
}
case 40:
{
call: clear();
top++;
call:draw();
//alert("Hi down");
break;
}
Default:
{
alert("Hi");
break;
}
}
}
}
);
function get_random_color()
{
var letters = '0123456789ABCDEF'.split('');
var color = '#';
for (var i = 0; i < 6; i++ )
{
color += letters[Math.round(Math.random() * 15)];
}
return color;
}
function clear()
{
ctx.width = ctx.width;
ctx.height = ctx.height;
ctx = $('#cgame')[0].getContext("2d");
ctx.clearRect(0,0,cgame.width,cgame.height);
}
function draw()
{
ctx.width = ctx.width;
ctx.height = ctx.height;
ctx = $('#cgame')[0].getContext("2d");
ctx.fillRect(left, top, width, height);
}
socket.emit('register', name );
$('#Name').hide();
$('#Game').hide();
$('#start').hide();
});
</script>
</head>
<body>
<label id="Game">Welcome to Node JS Gaming</label>
<input type='text' id ='Name'>
<input type='button' id="start" value= 'login' Onclick="welcome()" >
<div>
<canvas id= "cgame" style="border:1px solid #000000; width: 100%; height: 100%;"; onkeydown ="onkeydown"></canvas>
</div>
</body>
</html>
Attempted socket code:
var io = require('socket.io').listen(8000);
io.sockets.on('connection', function (socket)
{
socket.on('movement',function(left,top, width, height){});
socket.broadcast.emit('movement', {
});
});
}
);
//io.sockets.emit();
I have to pass the left top width and height values to the server so that the value is reflected on another client. Say for example, two clients are Chrome and Mozilla, whenever a user presses up, down, left or right the corresponding rectangle has to be moved. Similarly it should happen for other users as well.
I don't know how to pass the values. Sorry for being so naive; I am a beginner in node.js.
Please let me know what the appropriate code is for the server side.
Please see this question regarding a game that is very similar to what you are trying to achieve
EDIT: Just realised that that question omits the actual multiplayer section. My implementation of that code was as follows (not full code, only the important bits)
Client Side:
socket.on('message', function(msg) {
msg = JSON.parse(msg);
players[msg.player] = new Player(msg.name, msg.x, msg.y, msg.width, msg.height, msg.color);
});
In the Player class:
draw : function(){
context.fillStyle = this.color; // context is canvas.getContext('2d');
context.fillRect(this.x, this.y, this.width, this.height);
}
Now also on the client side you can have:
function update() {
context.clearRect(0, 0, 700, 300); // clearing the canvas (width = 700, height = 300)
for( var player in players ) {
players[player].draw();
}
}
var FPS = 60;
setInterval(update, 1000/FPS);
In my experience you'd be better off doing the actual moving of the players coordinates on the server side. So client presses Left -> sent via socket -> server adjusts players x to x-=1 and sends it back where it's then drawn.
Note that this is a crude version of the code required

Resources