Not able to make sprite interaction to work - javascript

I'm trying to build a simple game menu, containing some clickable sprites, which will be used as buttons to navigate around the menu.
( I'm using the latest pixi.js (v4.3.5) )
Structure of my app:
loader.module ( makes use of the pixi.loaders.Loader )
events.module (basic event sub/pub module)
base.view
menu.view ( extends base.view )
game.main
How it all works now ?
Both all resources and ui elements have to be explicitly defined, before initializing the view.
so in the menu.view, the follwoing attributes have to be defined.
this.resources = [
{ name: 'start', src: 'img/start.png'},
{ name: 'start2', src: 'img/start2.png'}
];
this.ui = [{
name: 'start', /// resource name
type: 'img',
pos: { x: 0, y: 0 }
}];
Now I only need to call view.init() to get it all loaded and drawn. On the visual side, evrything works perfectly, however the 'start' sprite (which has it's interactive and buttonMode set to true) is not reacting to any mouse events.
The below method is responsible for getting the required resource and returning a new sprite. It also enables the actual interaction functionality as part of this process. But the test function is never triggered for some reason.
__getSpriteObject( element ){
let sprite = new PIXI.Sprite( this.loader.loader.resources[ element.name ].texture );
sprite.x = element.pos.x;
sprite.y = element.pos.y;
sprite.interactive = true;
sprite.buttonMode = true;
console.log( sprite )
sprite.on('pointerdown', function(){
console.log("what")
})
return sprite;
}
If the above info isn't sufficient , here is also a working example.

I answered this here: http://www.html5gamedevs.com/topic/28474-not-able-to-make-sprite-interaction-to-work/?do=findComment&comment=163647 in any case anyone else is curious

Related

Phaser 3: Change "Hitbox"/Interactive area of sprite without physics

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>

How to create a Konva-React context menu

To the best of my knowledge there isn't an easy/built in way with Konva to create a context menu for right clicking on objects. I am busy working on a project which requires the use of context menus, so I thought I'd just create my own.
Needless to say I am fairly new to Konva, so I was hoping someone on SO might have more experience to help me get over the last hurdles.
I have create a sandbox, located HERE
The requirements are:
An object should be draggable. (I copied a working example off the Konva sandbox.)
An object should show a context menu when right clicked upon.
The context menu should be dynamic, thus allow for multiple items, each executing its own callback when clicked upon.
Once a selection has been made, the context menu should be closed.
Thus far I have gotten most of it right, but the things I am struggling with are:
I cannot figure out how to hover over one context menu item, have it highlighted, then move to the next which should be highlighted and the old one restored to original settings.
Moving out of the context menu repaints the whole object. I don't understand why.
Clicking on one items fires both item's callbacks. Why? I a targeting the specific menu item which was clicked on, but getting both?
This point is less of a bug and more that I am unsure as how to proceed: How would I prevent multiple context menus to be create if a user right clicks multiple times on the object? Conceptually I understand that I could search for any items in a layer(?) with the name of the context menu and close it, however I have no idea how to do this.
I would appreciate any help. Thanks in advance.
Not sure if I'm late but I would use React Portals, theres a example about it on the react-konva page: https://konvajs.github.io/docs/react/DOM_Portal.html
I forked your sandbox with how this would be done: https://codesandbox.io/s/km0n1x8367
Not in react but plain JS I am afraid, but it shines a light on some of what you will have to do.
Click the pink circle, then take option 2 and click sub-option 2.
Areas requiring more work:
deliver the menu config data via JSON
make adding callbacks a method within the class
add a timeout on the hide to allow shaky mouse hands
how to handle hiding sub-menus when user mouse-outs or clicks another option
add reveal & hide animations
// Set up the canvas / stage
var stage = new Konva.Stage({container: 'container1', width: 600, height: 300});
// Add a layer some sample shapes
var layer = new Konva.Layer({draggable: false});
stage.add(layer);
// draw some shapes.
var circle = new Konva.Circle({ x: 80, y: 80, radius: 30, fill: 'Magenta'});
layer.add(circle);
var rect = new Konva.Rect({ x: 80, y: 80, width: 60, height: 40, fill: 'Cyan'});
layer.add(rect);
stage.draw();
// that is the boring bit over - now menu fun
// I decided to set up a plain JS object to define my menu structure - could easily receive from async in JSON format. [Homework #1]
var menuData = { options: [
{key: 'opt1', text: 'Option 1', callBack: null},
{key: 'opt2', text: 'Option 2', callBack: null,
options: [
{key: 'opt2-1', text: 'Sub 1', callBack: null},
{key: 'opt2-2', text: 'Sub 2', callBack: null}
]
},
{key: 'opt3', text: 'Option 3', callBack: null},
{key: 'opt4', text: 'Option 4', callBack: null}
]};
// Define a menu 'class' object.
var menu = function(menuData) {
var optHeight = 20; // couple of dimension constants.
var optWidth = 100;
var colors = ['white','gold'];
this.options = {}; // prepare an associative list accessible by key - will put key into the shape as the name so we can can get from click event to this entry
this.menuGroup = new Konva.Group({}); // prepare a canvas group to hold the option rects for this level. Make it accessible externally by this-prefix
var _this = this; // put a ref for this-this to overcome this-confusion later.
// recursive func to add a menu level and assign its option components.
var addHost = function(menuData, hostGroup, level, pos){ // params are the data for the level, the parent group, the level counter, and an offset position counter
var menuHost = new Konva.Group({ visible: false}); // make a canvas group to contain new options
hostGroup.add(menuHost); // add to the parent group
// for every option at this level
for (var i = 0; i < menuData.options.length; i = i + 1 ){
var option = menuData.options[i]; // get the option into a var for readability
// Add a rect as the background for the visible option in the menu.
option.optionRect = new Konva.Rect({x: (level * optWidth), y: (pos + i) * optHeight, width: optWidth, height: optHeight, fill: colors[0], stroke: 'silver', name: option.key });
option.optionText = new Konva.Text({x: (level * optWidth), y: (pos + i) * optHeight, width: optWidth, height: optHeight, text: ' ' + option.text, listening: false, verticalAlign: 'middle'})
console.log(option.optionText.height())
option.optionRect
.on('mouseover', function(){
this.fill(colors[1])
layer.draw();
})
.on('mouseleave', function(){
this.fill(colors[0])
layer.draw();
})
// click event listener for the menu option
option.optionRect.on('click', function(e){
var key = this.name(); // get back the key we stashed in the rect so we can get the options object from the lookup list
if (_this.options[key] && (typeof _this.options[key].callback == 'function')){ // is we found an option and it has a real function as a callback then call it.
_this.options[key].callback();
}
else {
console.log('No callback for ' + key)
}
})
menuHost.add(option.optionRect); // better add the rect and text to the canvas or we will not see it
menuHost.add(option.optionText);
_this.options[option.key] = option; // stash the option in the lookup list for later retrieval in click handlers.
// pay attention Bond - if this menu level has a sub-level then we call into this function again.
if (option.options){
var optionGroup = addHost(option, menuHost, level + 1, i) // params 3 & 4 are menu depth and popout depth for positioning the rects.
// make an onclick listener to show the sub-options
option.callback = function(e){
optionGroup.visible(true);
layer.draw();
}
}
}
return menuHost; // return the konva group
}
// so - now we can call out addHost function for the top level of the menu and it will recurse as needed down the sub-options.
var mainGroup = addHost(menuData, this.menuGroup, 0, 0);
// lets be nice and make a show() method that takes a position x,y too.
this.show = function(location){
location.x = location.x - 10; // little offset to get the group under the mouse
location.y = location.y - 10;
mainGroup.position(location);
mainGroup.show(); // notice we do not draw the layer here - leave that to the caller to avoid too much redraw.
}
// and if we have a show we better have a hide.
this.hide = function(){
mainGroup.hide();
}
// and a top-level group listener for mouse-out to hide the menu. You might want to put a timer on this [Homework #3]
mainGroup.on('mouseleave', function(){
this.hide();
layer.draw();
})
// end of the menu class object.
return this;
}
// ok - now we can get our menu data turned into a menu
var theMenu = new menu(menuData);
layer.add(theMenu.menuGroup); // add the returned canvas group to the layer
layer.draw(); // and never forget to draw the layer when it is time!
//
// now we can add some arbitrary callbacks to some of the options.
//
// make a trivial function to pop a message when we click option 1
var helloFunc = function(){
alert('hello')
}
// make this the callback for opt1 - you can move this inside the menu class object as a setCallback(name, function()) method if you prefer [homework #2]
theMenu.options['opt1'].callback = helloFunc;
// put a function on sub2 just to show it works.
theMenu.options['opt2-2'].callback = function(){ alert('click on sub-2') };
// and the original reason for this - make it a context menu on a shape.
circle.on('click', function(e){
theMenu.show({x: e.evt.offsetX, y: e.evt.offsetY});
layer.draw();
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.min.js"></script>
<div id='container1' style="width: 300px, height: 200px; background-color: silver;"></div>

Loading Models in same position, so that it overlays

I'm using the multimodel loader of the Autodesk. When I load the same model to the viewer, the position of previous model shifts to a different position.
I'm using transformations, like translation,rotation and scaling, to adjust the position of the model so that is overlays over the first model. Is there a way to load the model in a way where it overlaps on loading the second model?
Also, for the z-oriented rvt and nwc files, is there a fix?
For reference, I have been following these blogs:
https://forge.autodesk.com/blog/preparing-your-viewing-application-multi-model-workflows
https://forge.autodesk.com/blog/preparing-your-viewing-application-multi-model-workflows-part-2-model-loader
It seems to have another placement or offsets applied to loaded models while using the ModelLoaderExtension. If you just want to load the same model twice,
here is a sample for you:
var models = [
'123.svf',
'123.svf'
];
function _onGeometryLoaded( event ) {
if( urns.length <= 0 ) {
viewer.removeEventListener(
Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
_onGeometryLoaded
);
return;
}
viewer.loadModel( urns[0], { globalOffset: event.model.getData().globalOffset } );
urns.splice( 0, 1 );
}
viewer.addEventListener(
Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
_onGeometryLoaded
);
viewer.loadModel( urns[0] );
urns.splice( 0, 1 );
Hope it helps!

How to lock slider and prevent updating of values with mouse into dat.GUI menu

I try to implement a way to prevent the updating of values with mouse (actually when the three.js animation has started, launched with a click on button).
For the moment, I have the following dat.GUI menu:
Once "start" button is clicked, I would like to prevent user from modifying with mouse the parameters "Rotation x" and "Rotation y".
Here is the concerned part of code for this menu:
// Create GUI
var gui = new dat.GUI({
autoplace: false,
width: 350,
height: 9 * 32 - 1
});
var params = {
GreatCircle : '',
Rotationx : torusRotationInitX,
Rotationy : torusRotationInitY,
StartingVector : '',
ComponentVectorTheta : 15.0,
ComponentVectorPhi : 15.0,
CovariantDerivativeVector : '',
ComponentCovariantDerivativeTheta : 15.0,
ComponentCovariantDerivativePhi : 15.0
};
// Set parameters for GUI
gui.add(params, 'GreatCircle').name('Great Circle ');
controllerRotationx = gui.add(params, 'Rotationx', 0, 2*Math.PI, 0.001).name('Rotation x ');
controllerRotationy = gui.add(params, 'Rotationy', 0, 2*Math.PI, 0.001).name('Rotation y ');
...
When I click on reset button, I call the following function:
// Reset Button
resetButton.onclick = function ResetParameters() {
...
// Reinitialize parameters into gui
params.Rotationx = torusRotationInitX;
params.Rotationy = torusRotationInitY;
for (var i in gui.__controllers) {
gui.__controllers[i].updateDisplay();
}
render();
}
I don't know if there is an option for controller to lock these sliders which usually change their values. Is it possible?
Update 1
Maybe I could wrapper the dat.GUI menu into a div and make this div not clickable, is it a solution?
Update 2
I tried to apply the method used on Method for disabling a button in dat.gui?
Following this solution, I have added the extension into dat.gui, just after:
dat.controllers.FunctionController = (function (Controller, dom, common) {
...
});
The following added code snippet is:
function blockEvent(event)
{
event.stopPropagation();
}
Object.defineProperty(dat.controllers.FunctionController.prototype, "disabled", {
get: function()
{
return this.domElement.hasAttribute("disabled");
},
set: function(value)
{
if (value)
{
this.domElement.setAttribute("disabled", "disabled");
this.domElement.addEventListener("click", blockEvent, true);
}
else
{
this.domElement.removeAttribute("disabled");
this.domElement.removeEventListener("click", blockEvent, true);
}
},
enumerable: true
});
Is extension code well located into dat.GUI source?
Then, I set the property "disabled" into my code to prevent user from sliding "controllerRotationx" with mouse (once start button is pressed):
if (animation)
controllerRotationx.__li.disabled = true;
Unfortunately, my method doesn't work : when animation is started, I can still move the slider contained into "controllerRotationx".
I saw that above link (Method for disabling a button in dat.gui?), this was about a button and not for a slider, does it change anything for my case?
I didn't find an explicit controller for the slider.
I would do this. The slider is not a form element, there's nothing to disable in the traditional w3c sense. Luckily we can use pointer-events and disable it properly as if it were a form element using just public dat.gui properties.
var speeder = menu.add(text, 'speed', -5, 5);
speeder.domElement.style.pointerEvents = "none"
speeder.domElement.style.opacity = .5;
The solution given by #Radio works pretty well. But, with sliders, the slider is a sibling of the text box's DOM element. We need to disable pointer events on the div which contains all the controls (and which is not exposed directly by dat.gui). So,
var speeder = menu.add(text, 'speed', -5, 5);
// disables the text box
speeder.domElement.style.pointerEvents = "none"
// disables all controller elements related to "speeder"
speeder.domElement.parentElement.style.pointerEvents = 'none'
When the Start button is pressed, set:
controllerRotationx.__li.setAttribute( "style", "display: none" );
thanks for tips
on my side i hack the Common controller
so able to chainning.
gui.add(this, '_screenW').disable(true);
Common.extend(controller, {
disable: function disable(v) {
this.domElement.style.pointerEvents = v?"none":"auto";
this.domElement.style.opacity = v?.5:1;
return controller;
},

Snap SVG animate SVG on hover / reset SVG on leave

I'm using Snap.svg to create some SVGs that animate on hover.
A very simplified jsfiddle is here:
http://jsfiddle.net/62UA7/2/
var s = Snap("#svg");
var smallCircle = s.circle(100, 150, 70);
//animation
function animateSVG(){
smallCircle.animate({cy: 300}, 5000,mina.bounce);
smallCircle.animate({fill:"red"},200);
}
//reset function?
function resetSVG(){
// something here to reset SVG??
}
smallCircle.mouseover(animateSVG,resetSVG);
The hover / animation is working fine.
The intention is to stop the animation and return to original SVG state if the user moves the mouse off the SVG - and this is where I'm currently stuck.
The actual SVG files I'm using are complex, so hoping for a quick way of 'refreshing' the SVG rather than manually moving it back to original position and colour
I'm assuming there's a really easy way of doing this - just can't seem to work it out or find the answer in any documentation anywhere.
Hopefully someone can help - thanks in advance if you can!
If you are only willing to animate between 2 states I found that Codrops animated svg icons did great job with handling this type of snap.svg animations. I have started using their code as a basis for my future exploration of SNAP.SVG. But getting back to the code: the most fun part is that you configure your animation with simple JSON objects such as:
plus : {
url : 'plus',
animation : [
{
el : 'path:nth-child(1)',
animProperties : {
from : { val : '{"transform" : "r0 32 32", "opacity" : 1}' },
to : { val : '{"transform" : "r180 32 32", "opacity" : 0}' }
}
},
{
el : 'path:nth-child(2)',
animProperties : {
from : { val : '{"transform" : "r0 32 32"}' },
to : { val : '{"transform" : "r180 32 32"}' }
}
}
]
},
and you can easily attach any sort of event trigger for animation In/Out. Hope that helps.
Personally I'd probably do it something like the following, storing it in a data element. It depends what problems you are really trying to overcome though, how you are actually animating it (I suspect it could be easier than my solution with certain animations, but trying to think of something that covers most bases), and if you really need it reset, also how many attributes you are animating and if there is other stuff going on...
var smallCircle = s.circle(100, 150, 70);
var saveAttributes = ['fill', 'cy'];
Snap.plugin( function( Snap, Element, Paper, global ) {
Element.prototype.resetSVG = function() {
this.stop();
for( var a=0; a<saveAttributes.length; a++) {
if( this.data( saveAttributes[a] ) ) {
this.attr( saveAttributes[a], this.data( saveAttributes[a] ) );
};
};
};
Element.prototype.storeAttributes = function() {
for( var a=0; a<saveAttributes.length; a++) {
if( this.attr( saveAttributes[a]) ) {
this.data( saveAttributes[a], this.attr( saveAttributes[a] ) );
};
};
};
});
function animateSVG(){
smallCircle.animate({cy: 300}, 5000,mina.bounce);
smallCircle.animate({fill:"red"},200);
};
smallCircle.storeAttributes();
smallCircle.mouseover( animateSVG );
smallCircle.mouseout( smallCircle.resetSVG );
jsfiddle

Categories

Resources