Hey I need to create a joystick for a camera control.
The joystick need to be able to move to four directions: left,right,up and down and two have functionality of stop.
I have search the web for hours, finally I found something which called nipplejs.
I have attached the code of nipplejs to this thread:
var radius = 100;
var sampleJoystick = {
mode: 'static',
position: {
left: '50%',
top: '50%'
},
size: radius*2,
color: 'black'
};
var joystick;
var position;
joystick = nipplejs.create(sampleJoystick);
joystick.on('start end', function(evt, data) {
position = data;
}).on('move', function(evt, data) {
position = data;
}).on('dir:up plain:up dir:left plain:left dir:down' +
'plain:down dir:right plain:right',
function(evt, data) {
//position=data;
}
).on('pressure', function(evt, data) {
position=data;
});
<script src="//yoannmoinet.github.io/nipplejs/javascripts/nipplejs.js"></script>
<div id="zone_joystick">
</div>
At first glance it looks like the perfect library, but on second look I can see that I have no directions indicators on the joystick and I can't implement a stop behavior with this joystick, and it won't be intuitive control for elders who will use the software.
I am looking for alternative solution for a panel control which have the functionalities which I have talked about.
functionalities:
joystick which can be turn to 4 different directions: Left,Right,Up and Down.
joystick have a stop functionality as well.
You have to connect to the hardware first, before you can read its input. Have you considered using the browser native GamePad API? This doesn't work in all browsers though.
https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API
Related
The game I'm creating doesn't require any physics, however you are able to interact when hovering over/clicking on the sprite by using sprite.setInteractive({cursor: "pointer"});, sprite.on('pointermove', function(activePointer) {...}); and similar. However I noticed two issues with that:
The sprite has some area which are transparent. The interactive functions will still trigger when clicking on those transparent areas, which is unideal.
When playing a sprite animation, the interactive area doesn't seem to entirely (at all?) change, thus if the sprite ends on a frame bigger than the previous, there end up being small areas I can't interact with.
One option I thought of was to create a polygon over my sprite, which covers the area I want to be interactive. However before I do that, I simply wanted to ask if there are simpler ways to fix these issues.
Was trying to find an answer for this myself just now..
Think Make Pixel Perfect is what you're looking for.
this.add.sprite(x, y, key).setInteractive(this.input.makePixelPerfect());
https://newdocs.phaser.io/docs/3.54.0/focus/Phaser.Input.InputPlugin-makePixelPerfect
This might not be the best solution, but I would solve this problem like this. (If I don't want to use physics, and if it doesn't impact the performance too much)
I would check in the event-handler, if at the mouse-position the pixel is transparent or so, this is more exact and less work, than using bounding-boxes.
You would have to do some minor calculations, but it should work well.
btw.: if the origin is not 0, you would would have to compensate in the calculations for this. (in this example, the origin offset is implemented)
Here is a demo, for the click event:
let Scene = {
preload ()
{
this.load.spritesheet('brawler', 'https://labs.phaser.io/assets/animations/brawler48x48.png', { frameWidth: 48, frameHeight: 48 });
},
create ()
{
// Animation set
this.anims.create({
key: 'walk',
frames: this.anims.generateFrameNumbers('brawler', { frames: [ 0, 1, 2, 3 ] }),
frameRate: 8,
repeat: -1
});
// create sprite
const cody = this.add.sprite(200, 100).setOrigin(0);
cody.play('walk');
cody.setInteractive();
// just info text
this.mytext = this.add.text(10, 10, 'Click the Sprite, or close to it ...', { fontFamily: 'Arial' });
// event to watch
cody.on('pointerdown', function (pointer) {
// calculate x,y position of the sprite to check
let x = (pointer.x - cody.x) / (cody.displayWidth / cody.width)
let y = (pointer.y - cody.y) / (cody.displayHeight / cody.height);
// just checking if the properties are set
if(cody.anims && cody.anims.currentFrame){
let currentFrame = cody.anims.currentFrame;
let pixelColor = this.textures.getPixel(x, y, currentFrame.textureKey, currentFrame.textureFrame);
// alpha > 0 a visible pixel of the sprite, is clicked
if(pixelColor.a > 0) {
this.mytext.text = 'Hit';
} else {
this.mytext.text = 'No Hit';
}
// just reset the textmessage
setTimeout(_ => this.mytext.text = 'Click the Sprite, or close to it ...' , 1000);
}
}, this);
}
};
const config = {
type: Phaser.AUTO,
width: 400,
height: 200,
scene: Scene
};
const game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
I have recently started working with leaflet. I found the great plugin leaflet markercluster. I am currently trying to open several clustergroups at the same time. Unfortunately I can't find anything on google.
I have several cluster groups and draw connections from one marker to another. The user should be able to open both cluster groups to which the drawn line goes:
Therefore my question: Is there a function for this that I have to switch on or is opening several groups at the same time not provided at all?
Okay I have experimented a little bit on it now ;)
In the leaflet.markercluster-src.js I created an array called _spiderMan[] which is filled with the clicked objects in the function spiderfy.
spiderfy: function() {
if (this._group._spiderfied === this || this._group._inZoomAnimation) {
return;
}
var childMarkers = this.getAllChildMarkers(null, true),
group = this._group,
map = group._map,
center = map.latLngToLayerPoint(this._latlng),
positions;
// this._group._unspiderfy(); //deactivated
var markers = markers + childMarkers;
_spiderMan.push(this); //new
if (childMarkers.length >= this._circleSpiralSwitchover) {
positions = this._generatePointsSpiral(childMarkers.length, center);
} else {
center.y += 10;
positions = this._generatePointsCircle(childMarkers.length, center);
}
this._animationSpiderfy(childMarkers, positions);
},
Then I have created a for loop which runs through the array and calls _spiderMan[i].unspiderfy(zoomDetails) every time. I built this loop into the function _unspiderfyZoomAnim for testing. Means every time you zoom in or out, all open groups are summarized.
_unspiderfyZoomAnim: function(zoomDetails) {
if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) {
return;
}
this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
var i;
for (i = 0; i < _spiderMan.length; i++) {
_spiderMan[i].unspiderfy(zoomDetails);
}
_spiderMan = [];
},
In addition, the following lines must be deactivated in the unspiderfy function:
unspiderfy: function(zoomDetails) {
/// <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param>
// if (this._group._inZoomAnimation) {
// return;
// }
this._animationUnspiderfy(zoomDetails);
// this._group._spiderfied = null;
},
So it's now possible to open and close mutiple cluster-groups but this is only a workaround and I think it will have some bad effects somewhere because of commenting out or removing code lines.
I think someone with more experience in JS and this plugin should find a better and more comfortable solution ;).
Welcome to SO!
Unfortunately, the spiderfication management in Leaflet.markercluster plugin currently assumes a single cluster can be spiderfied at a time.
See also danzel's comment in Leaflet.markercluster issue #744 (Spiderfy all clusters at a particular view):
Leaflet.MarkerCluster only supports having one cluster spiderfied at the moment, so this would need a bit of work to support.
May be you will get a better answer if you give a use case ...
However, it is safe to say that there is no function you can switch on to open several groups in one click.
From a usability point of view, it does not make much sense as the basic behaviour of MarkerCluster is to click on one icon to zoom in and expand the group you are interested in.
Quoting from https://github.com/Leaflet/Leaflet.markercluster#other-clusters-methods :
spiderfy: Spiderfies the child markers of this cluster
unspiderfy: Unspiderfies a cluster (opposite of spiderfy)
So once you have references to the clusters you want to "open" (spiderify) at the same time, just call their .spiderify() method.
e.g. if the desired clusters are in variables cluster1 and cluster2:
cluster1.spiderify();
cluster2.spiderify();
See also https://github.com/Leaflet/Leaflet.markercluster#getting-the-visible-parent-of-a-marker and https://github.com/Leaflet/Leaflet.markercluster#clusters-methods about how to get references to the clusters.
As far as I can tell you can keep open multiple clusters, but only one for each group. My guess is that your markers all belong to a single group. In which case you can't keep open multiple clusters.
You could opt for a hover approach, which opens a cluster if you hover over it.
const mymap = L.map('mapid').setView([48.550, 8.207], 6);
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
maxZoom: 18,
id: 'mapbox.streets'
}).addTo(mymap);
const markers = L.markerClusterGroup({zoomToBoundsOnClick: false});
[[47.5617, 7.5504], [47.5255, 7.6163], [47.5691, 7.6355],
[49.4922, 8.3922], [49.5306, 8.5172], [49.4547, 8.5062]]
.map(latLng => L.marker(latLng))
.forEach(marker => markers.addLayer(marker));
mymap.addLayer(markers);
markers.on("clustermouseover", a => a.layer.spiderfy());
markers.on("clustermouseout", a => a.layer.unspiderfy());
html, body, #mapid { margin: auto; height: 100%; }
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.5.1/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet#1.5.1/dist/leaflet.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster#1.4.1/dist/MarkerCluster.Default.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster#1.4.1/dist/MarkerCluster.css" />
<script src="https://unpkg.com/leaflet.markercluster#1.4.1/dist/leaflet.markercluster.js"></script>
<div id="mapid"></div>
I currently have a giant table of "auditpoints", some of those points are "automated". If they are automated they receive a gear icon in their row. The gear icon is not the only icon each row receives. Each row, no matter if it's automated or not receives two other icons, a pencil and a toggle button. When an automated point "runs" the gear icon rotates until it is finished "running". I've have implemented some code to ran all of these points at once but I have a small problem. When you click my button to run all these points all three of the icons I have mentioned rotate and this is not the result I am looking for. The line commented out in my code snippet (and it's matching bracket) will prevent the code from running all of the automated points. Commenting out the line is what causes all the icons to rotate. I know this line is required to get the automated points to run properly as it used in the single execution of automated points I just don't know what to change it to. It obviously shouldn't be click because you are no longer clicking the gear icon to get a point to run I just don't know what to change it to but the classes in that click function are related to the gear icon.
Hopefully this is a very easy question to solve and doesn't waste anyone's time. Thank you!
private updateAuto() {
var self = this;
$(".auditPointRow").each(function () {
//self.el.on("click", ".update, .edit", function () {
var row = $(this).closest(".auditPointRow");
var id = row.data("id");
var automated = (<string>row.data("automated")).toLowerCase() == "true";
var running = true;
if (automated && $(this).closest(".edit").length == 0) {
var gear = $(this).find(".fa");
var maxTurns = 120;
gear.css("transition", "transform linear " + maxTurns * 2 + "s");
gear.css("transform", "rotate(" + (maxTurns * 360) + "deg)");
var request = $.ajax(self.root + "api/sites/" + self.site.ID + "/auditpoints/" + id, {
"type": "PATCH", data: JSON.stringify([
{
Op: "Replace"
, Path: "/score"
, Value: "run"
}
])
});
request.done(function () {
gear.css("transition", "").css("transform", "rotate(0deg)");
row.prev().find("td").css("background-color", "");
if (row.prev().qtip("api")) {
row.prev().qtip("api").destroy(true);
}
});
}
//}
});
}
I think I found a solution to my problem. I used .each again to go through all of the "gears" and only rotate them.
private updateAuto() {
var self = this;
//$(".auditPointRow").each(function () {
$(".update, .edit").each(function () {
//Left out the rest of the code so this answer isn't too
//long, none of it changed if that matters.
});
//});
}
For some reason the result runs very slowly (but it runs!) and I'm not sure why so if anyone has any better suggestion/optimizations please feel free to leave those here.
Edit: I realized I didn't to go through .each twice, that's what was slowing to down so I removed that first each that went over auditPoints and just did the ones with gears instead.
Is there a way one could zoom and pan on a canvas using KineticJS? I found this library kineticjs-viewport, but just wondering if there is any other way of achieving this because this library seems to be using so many extra libraries and am not sure which ones are absolutely necessary to get the job done.
Alternatively, I am even open to the idea of drawing a rectangle around the region of interest and zooming into that one particular area. Any ideas on how to achieve this? A JSFiddle example would be awesome!
You can simply add .setDraggable("draggable") to a layer and you will be able to drag it as long as there is an object under the cursor. You could add a large, transparent rect to make everything draggable. The zoom can be achieved by setting the scale of the layer. In this example I'm controlling it though the mousewheel, but it's simply a function where you pass the amount you want to zoom (positive to zoom in, negative to zoom out). Here is the code:
var stage = new Kinetic.Stage({
container: "canvas",
width: 500,
height: 500
});
var draggableLayer = new Kinetic.Layer();
draggableLayer.setDraggable("draggable");
//a large transparent background to make everything draggable
var background = new Kinetic.Rect({
x: -1000,
y: -1000,
width: 2000,
height: 2000,
fill: "#000000",
opacity: 0
});
draggableLayer.add(background);
//don't mind this, just to create fake elements
var addCircle = function(x, y, r){
draggableLayer.add(new Kinetic.Circle({
x: x*700,
y: y*700,
radius: r*20,
fill: "rgb("+ parseInt(255*r) +",0,0)"
})
);
}
var circles = 300
while (circles) {
addCircle(Math.random(),Math.random(), Math.random())
circles--;
}
var zoom = function(e) {
var zoomAmount = e.wheelDeltaY*0.001;
draggableLayer.setScale(draggableLayer.getScale().x+zoomAmount)
draggableLayer.draw();
}
document.addEventListener("mousewheel", zoom, false)
stage.add(draggableLayer)
http://jsfiddle.net/zAUYd/
Here's a very quick and simple implementation of zooming and panning a layer. If you had more layers which would need to pan and zoom at the same time, I would suggest grouping them and then applying the on("click")s to that group to get the same effect.
http://jsfiddle.net/renyn/56/
If it's not obvious, the light blue squares in the top left are clicked to zoom in and out, and the pink squares in the bottom left are clicked to pan left and right.
Edit: As a note, this could of course be changed to support "mousedown" or other events, and I don't see why the transformations couldn't be implemented as Kinetic.Animations to make them smoother.
this is what i have done so far.. hope it will help you.
http://jsfiddle.net/v1r00z/ZJE7w/
I actually wrote kineticjs-viewport. I'm happy to hear you were interested in it.
It is actually intended for more than merely dragging. It also allows zooming and performance-focused clipping. The things outside of the clip region aren't rendered at all, so you can have great rendering performance even if you have an enormous layer with a ton of objects.
That's the use case I had. For example, a large RTS map which you view via a smaller viewport region -- think Starcraft.
I hope this helps.
As I was working with Kinetic today I found a SO question that might interest you.
I know it would be better as a comment, but I don't have enough rep for that, anyway, I hope that helps.
These answers seems not to work with the KineticJS 5.1.0. These do not work mainly for the signature change of the scale function:
stage.setScale(newscale); --> stage.setScale({x:newscale,y:newscale});
However, the following solution seems to work with the KineticJS 5.1.0:
JSFiddle: http://jsfiddle.net/rpaul/ckwu7u86/3/
Unfortunately, setting state or layer draggable prevents objects not draggable.
Duopixel's zooming solution is good, but I would rather set it for stage level, not layer level.
Her is my solution
var stage = new Kinetic.Stage({
container : 'container',
width: $("#container").width(),
height: $("#container").height(),
});
var layer = new Kinetic.Layer();
//layer.setDraggable("draggable");
var center = { x:stage.getWidth() / 2, y: stage.getHeight() / 2};
var circle = new Kinetic.Circle({
x: center.x-100,
y: center.y,
radius: 50,
fill: 'green',
draggable: true
});
layer.add(circle);
layer.add(circle.clone({x: center.x+100}));
// zoom by scrollong
document.getElementById("container").addEventListener("mousewheel", function(e) {
var zoomAmount = e.wheelDeltaY*0.0001;
stage.setScale(stage.getScale().x+zoomAmount)
stage.draw();
e.preventDefault();
}, false)
// pan by mouse dragging on stage
stage.on("dragstart dragmove", function(e) {window.draggingNode = true;});
stage.on("dragend", function(e) { window.draggingNode = false;});
$("#container").on("mousedown", function(e) {
if (window.draggingNode) return false;
if (e.which==1) {
window.draggingStart = {x: e.pageX, y: e.pageY, stageX: stage.getX(), stageY: stage.getY()};
window.draggingStage = true;
}
});
$("#container").on("mousemove", function(e) {
if (window.draggingNode || !window.draggingStage) return false;
stage.setX(window.draggingStart.stageX+(e.pageX-window.draggingStart.x));
stage.setY(window.draggingStart.stageY+(e.pageY-window.draggingStart.y));
stage.draw();
});
$("#container").on("mouseup", function(e) { window.draggingStage = false } );
stage.add(layer);
http://jsfiddle.net/bighostkim/jsqJ2/
Looking for some advice (example would be great) on dragging and dropping SVGs using RaphaelJS.
I have found how to drag an object created withint Raphael
window.onload = function() {
var R = Raphael("canvas", 500, 500);
var c = R.circle(100, 100, 50).attr({
fill: "hsb(.8, 1, 1)",
stroke: "none",
opacity: .5
});
var start = function () {
...
},
move = function (dx, dy) {
...
},
up = function () {
...
};
c.drag(move, start, up);
};
But I need to be able to adapt this to work with a seperate SVG file(s) so for example
myPage.html has myImage.svg, I need to be able to drag myImage.svg around the 'canvas'.
I'm thinking something like
var c = R.SOMEMETHOD('myImage.svg');
...
c.drag(move, start, up);
For example.
Is there a way to do this and if so, an example would be brilliant!
This magic method doesn't exist in RaphaelJS. But there is a way to accomplish this. You can have a look at the raphael-svg-import project on GitHub which works well for basic svgs
Then, you'll want to use a grouping facility as you cannot use the Set functionnality of RaphaelJS
1 - import your SVG
2 - As you import, mark the elements so they belong to the same group
Enjoy !!