I trying to get back the selected value in the dropdown and attribute it to atom.name in order to change the atom name. By default there is ch2 molecule and when click on Na. Ch2 should be replace by Na but the problem is the scope of the event listener and the capacity to manage these two eventlistener. The one who manage the dropdown result
var a = document.getElementById('atomDropdown');
a.addEventListener('change', function() {
console.log(this.value);
}, false);
the console.log give here the right result and
the eventlistener which manage the position of the dropdown menu with
document.body.addEventListener('mouseup', e => {
let atom = atoms.find(a => distance(a.position, { x: e.pageX, y: e.pageY}) <= a.r)
atomDropdown.classList.remove('hidden')
if(atom){
atomDropdown.style.left = atom.position.x + 'px'
atomDropdown.style.top = (atom.position.y + atom.r) + 'px'
}
console.log(atom.name);
})
What I'm trying to do without success is to attribute atom.name to this value.
const canvas = document.createElement('canvas'),
context = canvas.getContext('2d'),
width = canvas.width = window.innerWidth,
height = canvas.height = window.innerHeight,
atoms = [],
bonds = [],
atomDropdown = document.getElementById('atomDropdown')
document.body.appendChild(canvas)
class Atom {
constructor({ x, y, name }){
this.position = {}
this.position.x = x
this.position.y = y
this.name = name
this.r = name.length * 10
atoms.push(this)
}
draw(){
let { position, name, r } = this,
{ x, y } = position
context.fillStyle = '#EEEEEE'
context.beginPath()
context.arc(x, y, r, 0, 2 * Math.PI)
context.fill()
context.fillStyle = '#000000'
context.font = '20px sans-serif'
context.textAlign = 'center'
context.fillText(name, x, y + 5)
}
}
class Bond {
constructor({ atom1, atom2, type }){
this.atom1 = atom1
this.atom2 = atom2
bonds.push(this)
}
draw(){
let { atom1, atom2 } = this
context.beginPath()
context.strokeStyle = '#000000'
context.moveTo(atom1.position.x, atom1.position.y)
context.lineTo(atom2.position.x, atom2.position.y)
context.stroke()
}
}
let hexagon = {
size: 100,
x: width/2,
y: height/2
}
let lastAtom
for (var side = 0; side < 7; side++) {
let newAtom = new Atom({
x: hexagon.x + hexagon.size * Math.cos(side * 2 * Math.PI / 6),
y: hexagon.y + hexagon.size * Math.sin(side * 2 * Math.PI / 6),
name: 'CH2'
})
if(lastAtom) new Bond({ atom1: lastAtom, atom2: newAtom })
if(side == 6) new Bond({ atom1: newAtom, atom2: atoms[0] })
lastAtom = newAtom
}
function render(){
context.fillStyle = '#FFFFFF'
context.fillRect(0,0,width,height)
bonds.map(b => b.draw())
atoms.map(a => a.draw())
}
render()
function distance(p1, p2){
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2))
}
var a = document.getElementById('atomDropdown');
a.addEventListener('change', function() {
console.log(this.value);
}, false);
document.body.addEventListener('mouseup', e => {
let atom = atoms.find(a => distance(a.position, { x: e.pageX, y: e.pageY}) <= a.r)
atomDropdown.classList.remove('hidden')
if(atom){
atomDropdown.style.left = atom.position.x + 'px'
atomDropdown.style.top = (atom.position.y + atom.r) + 'px'
}
console.log(atom.name);
})
#atomDropdown {
position: absolute;
&.hidden {
display: none;
}
}
<select id="atomDropdown" class="hidden">
<option>Test1</option>
<option>Test2</option>
<option>Test3</option>
</select>
It seems like the desired behavior is to change the value of atom.name for the atom that was clicked, replacing it with a name from the dropdown menu.
E.g., click on a CH2 atom -> select "Test1" from the dropdown menu -> the value of this.name for the atom you click changes from "CH2" to "Test1".
If that's the case, the issue is how to target the same atom from your last "mouseup" event in the "change" handler for atomDropdown. In which case, you can add a new variable in your definitions:
const canvas = document.createElement('canvas'),
context = canvas.getContext('2d'),
width = canvas.width = window.innerWidth,
height = canvas.height = window.innerHeight,
atoms = [],
bonds = [],
atomDropdown = document.getElementById('atomDropdown')
var selectedAtom = null
set selectedAtom to the atom instance in the "mouseup" handler:
document.body.addEventListener('mouseup', e => {
let atom = atoms.find(a => distance(a.position, { x: e.pageX, y: e.pageY}) <= a.r)
atomDropdown.classList.remove('hidden')
if(atom){
selectedAtom = atom
atomDropdown.style.left = atom.position.x + 'px'
atomDropdown.style.top = (atom.position.y + atom.r) + 'px'
}
console.log(atom.name);
})
and update selectedAtom.name in the "change event":
var a = document.getElementById('atomDropdown');
a.addEventListener('change', function() {
if (selectedAtom) {
selectedAtom.name = this.value;
render(); //added so that the GUI updates when the name value changes
}
console.log(this.value);
}, false);
EDIT: to immediately update the name of the atom as it appears in the GUI/display, you also have to call render() after selectedAtom.name is changed in atomDropDown's "change" event.
If I'm understanding this question correctly, you're looking to set the dropdown menu's value to atom.name?
If so, you need to add an attribute "value" to the options, then you can say something like:
document.getElementById("atomDropdown").value = atom.name
The HTML would look something like this:
<select id="atomDropdown" class="hidden">
<option value="Na">Test1</option>
<option value="Ch2">Test2</option>
<option value="O2">Test3</option>
</select>
Related
I'm working on a small flappy-bird-like-game demo. Everthing seems fine, but I have a small problem/question.
I setup a collider function, and the callback works as expected, when the "two" objects collide, but there is a strange behavior:
the white-square (the bird) can fly through the obstacles, when coming from the side
but cannot passthrough when coming from below or on above
BUT the callback is execute always.
blue arrow marks where the square passes through
green arrows mark where the square doesn't passthrough
I'm not sure if this is, because I'm using rectangles (they sometimes) cause problems, or because of my nested physics setup. I even tried to replaced the white rectangel with a sprite, but this still produces the same result/error.
For my demo: I could probablly just destroy the object and restart the level on collision, but I still would like to understand why this is happening? And how I can prevent this, inconsistant behavior.
I'm probably missing something, but couldn't find it, and I don't want to rebuild the application again.
So my question is: why is this happening? And How can I prevent this?
Here is the code:
const width = 400;
const height = 200;
const spacing = width / 4;
const levelSpeed = width / 4;
const flySpeed = width / 4;
var GameScene = {
create (){
let player = this.add.rectangle(width / 4, height / 2, 20, 20, 0xffffff);
this.physics.add.existing(player);
this.input.keyboard.on('keydown-SPACE', (event) => {
if(player.body.velocity.y >= -flySpeed/2){
player.body.setVelocityY(-flySpeed);
}
});
player.body.onWorldBounds = true;
player.body.setCollideWorldBounds(true );
this.physics.world.on("worldbounds", function (body) {
console.info('GAME OVER');
player.y = height / 2;
player.body.setVelocity(0);
});
this.pipes = [];
for(let idx = 1; idx <= 10; idx++) {
let obstacle = this.createObstacle(spacing * idx, Phaser.Math.Between(-height/3, 0));
this.add.existing(obstacle);
this.pipes.push(obstacle);
this.physics.add.collider(obstacle.list[0], player)
this.physics.add.collider(obstacle.list[1], player, _ => console.info(2))
}
},
update(){
this.pipes.forEach((item) => {
if(item.x <= 0){
item.body.x = spacing * 10;
}
})
},
extend: {
createObstacle (x, y){
let topPipe = (new Phaser.GameObjects.Rectangle(this, 0, 0 , 20 , height / 2 ,0xff0000)).setOrigin(0);
let bottomPipe = (new Phaser.GameObjects.Rectangle(this, 0, height/2 + 75, 20 , height / 2 ,0xff0000)).setOrigin(0);
this.physics.add.existing(topPipe);
this.physics.add.existing(bottomPipe);
topPipe.body.setImmovable(true);
topPipe.body.allowGravity = false;
bottomPipe.body.setImmovable(true);
bottomPipe.body.allowGravity = false;
let obstacle = new Phaser.GameObjects.Container(this, x, y, [
topPipe,
bottomPipe
]);
this.physics.add.existing(obstacle);
obstacle.body.velocity.x = - levelSpeed;
obstacle.body.allowGravity = false;
return obstacle;
}
}
};
var config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width,
height,
scene: [GameScene],
physics: {
default: 'arcade',
arcade: {
gravity: { y: flySpeed },
debug: true
},
}
};
var game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Currently I just can assume, that the physics objects don't seem to work correct, when physics objects are nested.
Maybe I'm wrong, but since I rewrote the code again without nested physics - objects and it seems to work, I think my assumption Is correct. I shouldn't have tried to over engineer my code.
If someone has more insides, please let me know/share. I still not 100% sure, if this is the real reason, for the strange behavior.
Here the rewriten code:
const width = 400;
const height = 200;
const spacing = width / 4;
const levelSpeed = width / 4;
const flySpeed = width / 4;
var GameScene = {
create (){
let player = this.add.rectangle(width / 4, height / 2, 20, 20, 0xffffff);
this.physics.add.existing(player);
this.input.keyboard.on('keydown-SPACE', (event) => {
if(player.body.velocity.y >= -flySpeed/2){
player.body.setVelocityY(-flySpeed);
}
});
player.body.onWorldBounds = true;
player.body.setCollideWorldBounds(true );
this.physics.world.on("worldbounds", function (body) {
console.info('GAME OVER');
player.x = width / 4;
player.y = height / 2;
player.body.setVelocity(0);
});
this.pipes = [];
for(let idx = 1; idx <= 10; idx++) {
let obstacle = this.createObstacle(spacing * idx, Phaser.Math.Between(-height/3, 0));
this.add.existing(obstacle[0]);
this.add.existing(obstacle[1]);
this.pipes.push(...obstacle);
this.physics.add.collider(obstacle[0], player)
this.physics.add.collider(obstacle[1], player, _ => console.info(2))
}
},
update(){
this.pipes.forEach((item) => {
if(item.x <= 0){
item.body.x = spacing * 10;
}
item.body.velocity.x = - levelSpeed;
})
},
extend: {
createObstacle (x, y){
let topPipe = (new Phaser.GameObjects.Rectangle(this, x, -20 , 20 , height / 2 ,0xff0000)).setOrigin(0);
let bottomPipe = (new Phaser.GameObjects.Rectangle(this, x, height/2 + 75, 20 , height / 2 ,0xff0000)).setOrigin(0);
this.physics.add.existing(topPipe);
this.physics.add.existing(bottomPipe);
topPipe.body.setImmovable(true);
topPipe.body.allowGravity = false;
topPipe.body.velocity.x = - levelSpeed;
bottomPipe.body.setImmovable(true);
bottomPipe.body.allowGravity = false;
bottomPipe.body.velocity.x = - levelSpeed;
return [topPipe, bottomPipe];
}
}
};
var config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width,
height,
scene: [GameScene],
physics: {
default: 'arcade',
arcade: {
gravity: { y: flySpeed },
debug: true
},
}
};
var game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
so I was playing around with the fabricjs canvas library and I found this fiddle written in vanillajs which lets you draw polygons on the canvas. I wanted to implement this exact thing in my react project so I tried to convert the entire code into react (https://codesandbox.io/s/jolly-kowalevski-tjt58). The code works somewhat but there are some new bugs which are not in the original fiddle and I'm having trouble fixing them.
for eg: try to create a polygon by clicking the draw button, when you do this first time, the polygon is drawn without any bugs, but when you click the draw button again for the second time, the canvas starts acting weird and a weird polygon is created.
So basically I need help in converting the vanilla code to react with 0 bugs.
extra information:
fabric version used in the fiddle: 4.0.0
fabric version in sandbox: 4.0.0
Vanilla Js Code:
const getPathBtn = document.getElementById("get-path");
const drawPolygonBtn = document.getElementById("draw-polygon");
const showPolygonBtn = document.getElementById("show-polygon");
const editPolygonBtn = document.getElementById("edit-polygon");
const canvas = new fabric.Canvas("canvas", {
selection: false
});
let line, isDown;
let prevCords;
let vertices = [];
let polygon;
const resetCanvas = () => {
canvas.off();
canvas.clear();
};
const resetVariables = () => {
line = undefined;
isDown = undefined;
prevCords = undefined;
polygon = undefined;
vertices = [];
};
const addVertice = (newPoint) => {
if (vertices.length > 0) {
const lastPoint = vertices[vertices.length - 1];
if (lastPoint.x !== newPoint.x && lastPoint.y !== newPoint.y) {
vertices.push(newPoint);
}
} else {
vertices.push(newPoint);
}
};
const drawPolygon = () => {
resetVariables();
resetCanvas();
canvas.on("mouse:down", function(o) {
isDown = true;
const pointer = canvas.getPointer(o.e);
let points = [pointer.x, pointer.y, pointer.x, pointer.y];
if (prevCords && prevCords.x2 && prevCords.y2) {
const prevX = prevCords.x2;
const prevY = prevCords.y2;
points = [prevX, prevY, prevX, prevY];
}
const newPoint = {
x: points[0],
y: points[1]
};
addVertice(newPoint);
line = new fabric.Line(points, {
strokeWidth: 2,
fill: "black",
stroke: "black",
originX: "center",
originY: "center",
});
canvas.add(line);
});
canvas.on("mouse:move", function(o) {
if (!isDown) return;
const pointer = canvas.getPointer(o.e);
const coords = {
x2: pointer.x,
y2: pointer.y
};
line.set(coords);
prevCords = coords;
canvas.renderAll();
});
canvas.on("mouse:up", function(o) {
isDown = false;
const pointer = canvas.getPointer(o.e);
const newPoint = {
x: pointer.x,
y: pointer.y
};
addVertice(newPoint);
});
canvas.on("object:moving", function(option) {
const object = option.target;
canvas.forEachObject(function(obj) {
if (obj.name == "Polygon") {
if (obj.PolygonNumber == object.polygonNo) {
const points = window["polygon" + object.polygonNo].get(
"points"
);
points[object.circleNo - 1].x = object.left;
points[object.circleNo - 1].y = object.top;
window["polygon" + object.polygonNo].set({
points: points,
});
}
}
});
canvas.renderAll();
});
};
const showPolygon = () => {
resetCanvas();
if (!polygon) {
polygon = new fabric.Polygon(vertices, {
fill: "transparent",
strokeWidth: 2,
stroke: "black",
objectCaching: false,
transparentCorners: false,
cornerColor: "blue",
});
}
polygon.edit = false;
polygon.hasBorders = true;
polygon.cornerColor = "blue";
polygon.cornerStyle = "rect";
polygon.controls = fabric.Object.prototype.controls;
canvas.add(polygon);
};
// polygon stuff
// define a function that can locate the controls.
// this function will be used both for drawing and for interaction.
function polygonPositionHandler(dim, finalMatrix, fabricObject) {
let x = fabricObject.points[this.pointIndex].x - fabricObject.pathOffset.x,
y = fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y;
return fabric.util.transformPoint({
x: x,
y: y
},
fabric.util.multiplyTransformMatrices(
fabricObject.canvas.viewportTransform,
fabricObject.calcTransformMatrix()
)
);
}
// define a function that will define what the control does
// this function will be called on every mouse move after a control has been
// clicked and is being dragged.
// The function receive as argument the mouse event, the current trasnform object
// and the current position in canvas coordinate
// transform.target is a reference to the current object being transformed,
function actionHandler(eventData, transform, x, y) {
let polygon = transform.target,
currentControl = polygon.controls[polygon.__corner],
mouseLocalPosition = polygon.toLocalPoint(
new fabric.Point(x, y),
"center",
"center"
),
polygonBaseSize = polygon._getNonTransformedDimensions(),
size = polygon._getTransformedDimensions(0, 0),
finalPointPosition = {
x: (mouseLocalPosition.x * polygonBaseSize.x) / size.x +
polygon.pathOffset.x,
y: (mouseLocalPosition.y * polygonBaseSize.y) / size.y +
polygon.pathOffset.y,
};
polygon.points[currentControl.pointIndex] = finalPointPosition;
return true;
}
// define a function that can keep the polygon in the same position when we change its
// width/height/top/left.
function anchorWrapper(anchorIndex, fn) {
return function(eventData, transform, x, y) {
let fabricObject = transform.target,
absolutePoint = fabric.util.transformPoint({
x: fabricObject.points[anchorIndex].x -
fabricObject.pathOffset.x,
y: fabricObject.points[anchorIndex].y -
fabricObject.pathOffset.y,
},
fabricObject.calcTransformMatrix()
),
actionPerformed = fn(eventData, transform, x, y),
newDim = fabricObject._setPositionDimensions({}),
polygonBaseSize = fabricObject._getNonTransformedDimensions(),
newX =
(fabricObject.points[anchorIndex].x -
fabricObject.pathOffset.x) /
polygonBaseSize.x,
newY =
(fabricObject.points[anchorIndex].y -
fabricObject.pathOffset.y) /
polygonBaseSize.y;
fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5);
return actionPerformed;
};
}
function editPolygon() {
canvas.setActiveObject(polygon);
polygon.edit = true;
polygon.hasBorders = false;
let lastControl = polygon.points.length - 1;
polygon.cornerStyle = "circle";
polygon.cornerColor = "rgba(0,0,255,0.5)";
polygon.controls = polygon.points.reduce(function(acc, point, index) {
acc["p" + index] = new fabric.Control({
positionHandler: polygonPositionHandler,
actionHandler: anchorWrapper(
index > 0 ? index - 1 : lastControl,
actionHandler
),
actionName: "modifyPolygon",
pointIndex: index,
});
return acc;
}, {});
canvas.requestRenderAll();
}
// Button events
drawPolygonBtn.onclick = () => {
drawPolygon();
};
showPolygonBtn.onclick = () => {
showPolygon();
};
editPolygonBtn.onclick = () => {
editPolygon();
};
getPathBtn.onclick = () => {
console.log("vertices", polygon.points);
};
On 2nd draw (click the draw button again for the second time), the line is always connected to same point. So there is a problem with prevCords.
By adding a console.log to handler function of "mouse:mouse" confirmed above statement:
fabricCanvas.on("mouse:move", function (o) {
console.log("mousemove fired", prevCords); // always the same value
if (isDown.current || !line.current) return;
const pointer = fabricCanvas.getPointer(o.e);
const coords = {
x2: pointer.x,
y2: pointer.y
};
line.current.set(coords);
setPrevCords(coords); // the line should connect to this new point
fabricCanvas.renderAll();
});
It's because of closure, the function handler of mouse:move will always remember the value of prevCords when it was created (i.e when you click on Draw button) not the value that was updated by setPrevCords
To solve above problem, simply use useRef to store prevCords (or use reference)
Line 6:
const [fabricCanvas, setFabricCanvas] = useState();
const prevCordsRef = useRef();
const line = useRef();
Line 35:
const resetVariables = () => {
line.current = undefined;
isDown.current = undefined;
prevCordsRef.current = undefined;
polygon.current = undefined;
vertices.current = [];
};
Line 65:
if (prevCordsRef.current && prevCordsRef.current.x2 && prevCordsRef.current.y2) {
const prevX = prevCordsRef.current.x2;
const prevY = prevCordsRef.current.y2;
points = [prevX, prevY, prevX, prevY];
}
Line 96:
prevCordsRef.current = coords;
One last suggestion is to change Line 89 (so the feature match the demo):
if (!isDown.current) return;
On summary:
Don't use useState for variable that must have latest value in another function handler. Use useRef instead
Use useState for prevCords is a wasted since React will re-render on every setState
So I am building a video game where some fireball drop down the screen. However, there is only one image crossing the screen at a time. I would like that the image is actually multiplied an number of times. To get an idea of what I am saying, here is an image:
But what I would like to do is instead of only having one image (fireball) going down the screen, I would like to have a bunch of images dropping down the screen.
Here is the code for the fireball:
//Fireball script
function fFireball(offset) {
return Math.floor(Math.random() * (window.innerWidth - offset))
}
let fireball = {x: fFireball(fireballElement.offsetWidth), y: 0}
const fireLoop = function() {
fireball.y += 2; fireballElement.style.top = fireball.y + 'px'
if (fireball.y > window.innerHeight) {
fireball.x = fFireball(fireballElement.offsetWidth)
fireballElement.style.left = fireball.x + 'px'; fireball.y = 0
}
}
fireballElement.style.left = fireball.x + 'px'
let fireInterval = setInterval(fireLoop, 1000 / 100)
And the image:
<img src="Photo/fireball.png" id="fireball">
Thanks!
Use document.createElement()
Demo : https://jsfiddle.net/hexzero/ukh1dpwn/
I wrote down several comments in the code below to help you understand it better. If you have any additional question don't hesitate to ask.
const fireballArray = [];
// You can add any additional attributes you need for your fire balls here like ids and class names.
function generateFireBallWithAttributes(el, attrs) {
for (var key in attrs) {
el.setAttribute(key, attrs[key]);
}
return el;
}
function createFireBalls(amount){
for (let i = 0; i <= amount; i++) {
fireballArray.push( // create an image element
generateFireBallWithAttributes(document.createElement("img"), {
src: "Photo/fireball.png",
width: "32",
height: "32",
})
);
}}
createFireBalls(10)
fireballArray.forEach((fireballElement) => {
// Just add the id of the game body here, so that you could append your fire balls
document.getElementById("game-body").appendChild(fireballElement);
const fireball = { x: fFireball(fireballElement.offsetWidth), y: 0 };
const fireLoop = function () {
fireball.y += 2;
fireballElement.style.top = fireball.y + "px";
if (fireball.y > window.innerHeight) {
fireball.x = fFireball(fireballElement.offsetWidth);
fireballElement.style.left = fireball.x + "px";
fireball.y = 0;
}
};
fireballElement.style.left = fireball.x + "px";
// I've randomised the interval
// you may want to implement your own version to get more control
let fireInterval = setInterval(fireLoop, 1000 / ((Math.random() * (125 - 75)) + 75));
});
function fFireball(offset) {
return Math.floor(Math.random() * (window.innerWidth - offset));
}
img {
position: absolute;
}
I got inspired by this post Setting multiple attributes for an element at once with JavaScript, when adding extra attributes. If you like it please show them some love.
You can simply create a new <img> element for each bullet
I created a snippet doing it the way you were doing it. However, it would be more efficient if you had a single loop that updates all of the images at once.
const create = (x, y, vx, vy) => {
const object = {
x,
y,
vx,
vy,
el: document.createElement("img")
}
object.el.src = "http://placehold.it/50x50";
object.el.style.left = `${object.x}px`;
object.el.style.top = `${object.y}px`;
document.body.appendChild(object.el);
const intervalID = setInterval(() => {
object.x += object.vx;
object.y += object.vy;
object.el.style.left = `${object.x}px`;
object.el.style.top = `${object.y}px`;
if (object.y > window.innerHeight) {
document.body.removeChild(object.el);
clearInterval(intervalID)
}
}, 20);
}
setInterval(() => {
const randX = Math.floor(Math.random() * (window.innerWidth - 50));
create(randX, -50, 0, 2);
}, 500);
body {
margin: 0;
overflow: hidden;
}
img {
position: absolute;
}
I am trying to create a class which creates a Crafty entity with specific properties. So far, the functions within the class do not run because 'this' refers to the window object
$(document).ready(function () {
Crafty.init(window.innerWidth, window.innerHeight);
var player = new controller(37,38,39,40);
player.d.color("red").attr({
w: 50,
h: 50,
x: 0,
y: 0
});
// Jump Height = velocity ^ 2 / gravity * 2
// Terminal Velocity = push * (1 / viscosity)
var gravity = 1;
var viscosity = 0.5;
var frame = (1 / 20);
var distanceMultiplier = 10; //pixels per meter
var timeMultiplier = 20; //relative to actual time
var keystart = [];
var keyboard = [];
function controller (controls) {
this.d = Crafty.e();
this.d.addComponent("2D, Canvas, Color, Collision");
this.d.collision();
this.d.mass = 1;
this.d.a = {
extradistance : 0,
velocity : 0,
acceleration : 0,
force : 0,
resistance : 0
};
this.d.a.push = 0;
this.d.v = {
extradistance : 0,
velocity : 0,
acceleration : 0,
force : 0
};
this.d.jumping = true;
this.d.onHit("Collision", function () {
var a = this.d.hit("Collision");
if (a) {
for (var b in a) {
this.d.x = this.d.x - a[b].normal.x * a[b].overlap;
this.d.y = this.d.y - a[b].normal.y * a[b].overlap;
if (a[b].normal.y < -0.5) {
this.d.jumping = false;
}
if (Math.abs(a[b].normal.x) < 0.2) {
this.d.v.velocity = this.d.v.velocity * a[b].normal.y * 0.2;
}
if (Math.abs(a[b].normal.y) < 0.2) {
this.d.a.velocity = this.d.a.velocity * a[b].normal.x * 0.2;
}
}
return;
}
});
this.d.physics = function () {
if (keyboard[arguments[1]] && !this.jumping) {
this.v.velocity = 5;
this.jumping = true;
}
if (keyboard[arguments[1]] && this.jumping) {
var now = new Date();
if (now.getTime() - keystart[arguments[1]].getTime() < 500) {
this.v.velocity = 5;
}
}
if (keyboard[arguments[0]] && keyboard[arguments[2]]) {
this.a.velocity = 0;
} else {
if (keyboard[arguments[0]]) {
this.a.velocity = -3;
}
if (keyboard[arguments[2]]) {
this.a.velocity = 3;
}
}
if (keyboard[arguments[3]]) {
this.v.velocity = -5;
}
this.a.force = this.a.push - this.a.resistance;
this.a.acceleration = this.a.force / this.mass;
this.a.velocity = this.a.velocity + (this.a.acceleration * frame);
this.a.extradistance = (this.a.velocity * frame);
this.a.resistance = this.a.velocity * viscosity;
this.attr({
x: (this.x + (this.a.extradistance * distanceMultiplier))
});
this.v.force = gravity * this.mass;
this.v.acceleration = this.v.force / this.mass;
this.v.velocity = this.v.velocity - (this.v.acceleration * frame);
this.v.extradistance = (this.v.velocity * frame);
this.attr({
y: (this.y - (this.v.extradistance * distanceMultiplier))
});
setTimeout(this.physics, (frame * 1000) / timeMultiplier);
};
this.d.listen = function(){ document.body.addEventListener("keydown", function (code) {
var then = new Date();
if (!keyboard[code.keyCode] && !this.jumping && code.keyCode == arguments[1]) { //only if not yet pressed it will ignore everything until keyup
keyboard[code.keyCode] = true; //start movement
keystart[code.keyCode] = then; //set time
}
if (!keyboard[code.keyCode] && code.keyCode != arguments[1]) { //only if not yet pressed it will ignore everything until keyup
keyboard[code.keyCode] = true; //start movement
keystart[code.keyCode] = then; //set time
}
});
};
}
player.d.physics();
player.d.listen();
document.body.addEventListener("keyup", function (code) {
keyboard[code.keyCode] = false;
});
});
In trying to put the functions as prototypes of the class, I run into a problem.
Crafty.init(500,500);
function block () {
block.d = Crafty.e("2D, Color, Canvas");
block.d.color("red");
block.d.attr({x:0,y:0,h:50,w:50});
}
block.d.prototype.green = function() {
this.color("green");
}
var block1 = new block();
block1.d.color();
If an object is defined in the constructor, I cannot use it to add a prototype to.
Generally in Crafty, we favor composition. That is, you extend an entity by adding more components to it. You can have kind of a hierarchy by having one component automatically add others during init.
I haven't looked through all of your example code, because there's a lot! But consider the second block:
function block () {
block.d = Crafty.e("2D, Color, Canvas");
block.d.color("red");
block.d.attr({x:0,y:0,h:50,w:50});
}
block.d.prototype.green = function() {
this.color("green");
}
var block1 = new block();
block1.d.color();
You're trying to combine Crafty's way of doing things (an entity component system) with classes in a way that's not very idiomatic. Better to do this:
// Define a new component with Crafty.c(), rather than creating a class
Crafty.c("Block", {
// On init, add the correct components and setup the color and dimensions
init: function() {
this.requires("2D, Color, Canvas")
.color("red")
.attr({x:0,y:0,h:50,w:50});
},
// method for changing color
green: function() {
this.color("green");
}
});
// Create an entity with Crafty.e()
block1 = Crafty.e("Block");
// It's not easy being green!
block1.green();
I'm working on a floorplan editor program which is implemented in part using HTML5 Canvas and KineticJS. The user can select from a couple of tools to place nodes on the canvas (rooms, hallways, staircases, etc), and can then create edges between the nodes to link them.
I'm running into a problem when placing a specific type of node on my canvas: I can add any number of most types of nodes I've got defined without any performance decrease, but as soon as I try and add more than a handful of 'room' nodes, they take longer and longer to render and the stage tap event handler which creates nodes becomes unresponsive. This issue only occurs on the iPad and not when I run the application from a desktop.
All nodes are comprised of a Kinetic Circle with a particular image to represent the type of node and some data such as x/y position, type, id, and a list of attached edges. The only difference between room nodes and other special nodes is an extra Kinetic Rect and a couple of variables to represent the room itself. When creating a new node, all the attributes are filled by the constructor function, then the node is passed to a setup function to register event handlers and a render function to render it based on its type.
I'm still fairly new with these technologies, so any advice, no matter how specific or general, will probably be helpful. I've attached what I think will be helpful snippets of code; if you need to see more or have questions about what's here please let me know.
Thank you so much for any time and effort.
=========================================================================
EDIT:
Removing the opacity attribute of the room nodes made a huge difference; the performance decrease is almost entirely negligible now. I'll post further updates if and when I work out why that's the case.
function Node(x, y, id, type, attr) {
this.x = x;
this.y = y;
this.nX = 0;
this.nY = 0;
this.type = type;
this.bbox;
this.id = id;
this.edges = {};
this.attr = {"zPos": 0,
"name": '',
"width": default_room_width,
"height": default_room_height};
this.render(x, y, node_radius);
}
Node.prototype.render = function(x, y, r){
//Create node on canvas
this.visual = null;
var _self = this;
if(this.type == "node" || this.type == "Hallway"){
this.visual = new Kinetic.Circle({
x: x,
y: y,
radius: r,
fill: '#2B64FF',
stroke: '#000000',
strokeWidth: 2,
draggable: true
});
nodeLayer.add(this.visual);
this.x = this.visual.x();
this.y = this.visual.y();
nodeLayer.draw();
this.visual.id = this.id;
this.setupNode(x, y, node_radius);
} else {
var image = new Image();
_self.visual = new Kinetic.Image({
x: x - (node_img_size / 2),
y: y - (node_img_size / 2),
image: image,
width: node_img_size,
height: node_img_size,
draggable: true
});
image.onload = function(){
_self.setupNode(x, y, node_radius);
nodeLayer.add(_self.visual);
_self.x = _self.visual.x();
_self.y = _self.visual.y();
nodeLayer.draw();
_self.visual.id = _self.id;
}
image.src = '../../img/' + this.type + 'tool.png';
var width = this.attr["width"];
var height = this.attr["height"];
if(this.type== "room") {
this.bbox = new Kinetic.Rect({
x: x,
y: y,
strokeWidth: 2,
stroke: "black",
fill: "black",
opacity: 0.5,
listening: true,
width: width,
height: height,
offset: {x:width/2, y:height/2}
});
setupRoom(this.bbox);
this.bbox.offsetX(default_room_width/2);
this.bbox.offsetY(default_room_height/2);
roomLayer.add(this.bbox);
roomLayer.draw();
}
}
}
//Bind event handlers for rooms
function setupRoom(room) {
room.on(tap, function(e) {
if(selectedRoom == this) {
selectedRoom = null;
this.setOpacity(0.5);
roomLayer.draw();
} else {
if(selectedRoom != null)
selectedRoom.setOpacity(0.5);
selectedRoom = this;
this.setOpacity(1);
roomLayer.draw();
}
});
room.on('mouseenter', function(e) {
is_hovering = true;
}).on('mouseleave', function(e) {
is_hovering = false;
});
}
Node.prototype.setupNode = function (x, y) {
var _self = this;
var c = this.visual;
c.on('mouseenter', function(e){
is_hovering = true;
}).on('mouseleave', function(e){
is_hovering = false;
});
c.on(tap, function(e){
if(is_adding_node == true && _self.type == "node" && linkerNode != _self) {
is_adding_node = false;
nodeDrag = false;
$.each(nodes, function(key, value) {
if(value.type == "node") {
value.visual.fill("#2B64FF");
}
});
nodeLayer.batchDraw();
joinNodes(linkerNode, _self);
} else {
handleNodeTap(e, _self);
}
});
c.on(dbltap, function(e){
console.log(_self.id);
if(selectedTool != "link"){
showMenuForNode(c);
}
});
//Drag event
c.on(touchstart, function(e){
nodeDrag = true;
}).on(touchmove, function(e){
var touchPos = stage.getPointerPosition();
var newX = c.x() + offsetX * -1;
var newY = c.y() + offsetY * -1;
_self.x = newX;
_self.y = newY;
if(_self.text != null) {
_self.text.x(_self.visual.x() - node_text_offset);
_self.text.y(_self.visual.y() - node_text_offset);
}
//Move room BB if set
if (_self.bbox != undefined) {
_self.bbox.x(_self.visual.x() + _self.visual.width()/2);
_self.bbox.y(_self.visual.y() + _self.visual.height()/2);
roomLayer.batchDraw();
}
//Update positions of connected edges
for(var x in _self.edges){
edges[_self.edges[x]].setPosition();
}
nodeLayer.batchDraw();
}).on(touchend, function(e){
currentNode = _self;
nodeDrag = false;
});
};