collinhover / impactplusplus

Impact++ is a collection of additions to ImpactJS with full featured physics, dynamic lighting, UI, abilities, and more.
http://collinhover.github.com/impactplusplus
MIT License
276 stars 59 forks source link

Question: Handling Hold Input on Entities #158

Open itsOgden opened 10 years ago

itsOgden commented 10 years ago

Hey guys,

Just trying to clarify the question I posted on the ImpactJS forums. I don't think I explained my issue very well so I'm going to try again :)

I'm wanting to (on any type of entity) get the inputPoint and do a few actions when it is tapped OR held. Do I do this with the handleInput method? I thought that is how I would do it, but I can't seem to get it to even register a console log when I hold.

Pattentrick commented 10 years ago

Hi @itsOgden,

yes you could do that within the handleInput of your game method and iterate through inputPoints = ig.input.getInputPoints(['tapped'], [true]); like it does in the original method:

https://github.com/collinhover/impactplusplus/blob/dev/lib/plusplus/core/game.js#L2210

So this console.log() won't show up for you?

javascript handleInput: function(){

this.parent();

if (ig.input.released('tap')) {

    console.log('I should show up in the console :O');

}

}

itsOgden commented 10 years ago

@Pattentrick, Unfortunately, it does not, which is extremely weird! As of right now, the only way I've been able to get touch input to work is by an entity extending from UIButton using activateComplete() or by using update(). In the case of the update I've had trouble registering the proper inputPoints. It's been rather frustrating.

I've been able to get around it up until now, but I've reach a point where I need to be able to hold & tap and it can't be on UI elements.

Pattentrick commented 10 years ago

That's strange! Could you provide a small demo of your project and upload it to GitHub, or to another place? I am AFK for the next few hours, but once I am back I could look at your code and we may figure out together what's going on.

itsOgden commented 10 years ago

That would be very helpful! A demo of what I currently have can be found here: http://itsogden.com/kcdevtemp/. I uploaded the project to github so you can look at it here: https://github.com/itsOgden/KillerColors

Demo controls: Move through "blob" queue with WASD, hold SHIFT and move with WASD to mix "blob" colors. Click on the grid to throw "blobs".

Don't judge, it still has a long way to go :)

collinhover commented 10 years ago

Have you tried adding the following to your clickable entities?

...
targetable: true,
initTypes: function() {
       this.parent();
       _ut.addType(ig.EntityExtended, this, 'type', "INTERACTIVE");
},
...
collinhover commented 10 years ago

Also, your demo project on github has the ImpactJS source code in it. You should delete the github repo and upload it without the source code.

itsOgden commented 10 years ago

Thanks @collinhover, I completely forgot about that. I've never put a project up in any way so I didn't even think about it. Good catch.

Unfortunately, no dice on your addition. Even with that, it still will only work with the update.

Pattentrick commented 10 years ago

@itsOgden I get an 404 error when I am trying to get a local copy of your game running in my browser. The file that is missing is named lib/impact/gaPooling.js. Is this a plugin, or part of an older ImpactJS release? By the way, I am using version 1.23.

@collinhover What is the story behind INTERACTIVE in additon to targetable? Does this result in calling the activate method of the entitiy once it get's tapped/clicked?

Offtopic:

That really not belongs here, but I found a special piece of code yesterday that I have to share.

[2,3,4][+true]

Will return 3 by the way. Good cryptic way to retrieve an item from an array xD

itsOgden commented 10 years ago

Woops! It's a plugin that I forgot not to delete. I added it back and it should be good to go now.

On the off topic: How exactly does that work? I'm trying to figure it out and I'm stumped lol

Pattentrick commented 10 years ago

@itsOgden Great, works now! I will have a closer look at your code in a few hours. I am more into pixelart for myself, but i really love the smooth animation of your player. Also I kind of like the idea to select certain colors to defeat a specific monster. This has potential and is quite good for a first demo.

About the cryptic code: since JS is loosely typed, the interpreter will try to convert other values, that are not a number, to a number if you want to calculate with them. For example, true will be converted to 1, and false to 0. So true + true + 3 will be 5. And + true becomes 1.

So the [2,3,4][+true] is a very "creative" way to do this:

javascript var arr = [2,3,4]; arr[1] // gets the second item of arr

itsOgden commented 10 years ago

Haha that's fantastic. Does it have any practical applications?

And thanks for the compliments!

Pattentrick commented 10 years ago

@itsOgden

I think i solved your problem. When you call the parent method of the handleInput method, you won't be able to use the same inputPoint inside the child method. I think this is because of this part:

https://github.com/collinhover/impactplusplus/blob/master/lib/plusplus/core/game.js#L2236

But this is more like a wild guess, than a real explanation, very unsure about that one. Maybe @collinhover could clear that up for us. Fortunately we can work around that, just overwrite the handleInput method at your main.js like that:

javascript handleInput: function(){

var i, il;
var j, jl;
var inputPoints;
var inputPoint;
var targets;
var target;

if (ig.input.released('tap')) {

    // find all inputs that are tapping

    inputPoints = ig.input.getInputPoints(['tapped'], [true]);

    for (i = 0, il = inputPoints.length; i < il; i++) {

        inputPoint = inputPoints[i];
        targets = inputPoint.targets;

        if (targets.length > 0) {

            // check input targets

            for (j = 0, jl = targets.length; j < jl; j++) {

                target = targets[j];

                // toggle activation

                target.toggleActivate();

            }

        }

    }

}

}


Also make sure to add a type to your interactive entities and set the target property to true, like @collinhover has stated in the comment above:

``` javascript```
targetable: true,

initTypes: function() {

    this.parent();
    _ut.addType(ig.EntityExtended, this, 'type', "INTERACTIVE");

}

After that you should add an activate method to all of your interactive entities:

activate: function(){
    // Do stuff here ...
}

For actions based on hold just use the specific event. Hope this helps you.

itsOgden commented 10 years ago

Thanks @Pattentrick. I would love to have an explanation behind it, but I'll see if I can get it to work this way in the mean time!

collinhover commented 10 years ago

@Pattentrick you rock man! As far as the interactive type and targetable go, they are both there because targetable is intended for entities that can be targeted with something like an ability, while interactive type is more for mouse interaction. I think. To be fair, the current mouse handling system is not so good and is far too confusing :(

itsOgden commented 10 years ago

This is the first area I've had a real problem with. A few other things required some extensive researching to figure out, but all were understandable once that was taken care of. I still don't have a very firm grasp on what exactly is happening here.

I have tapping working now with the changes that you guys suggested, but I assumed that the following would solve my problem for holding and to no avail. The code for 'tapped' is all over the place, but no where is the same thing used for holding so I am kinda winging it:

    handleInput: function(){
        var i, il;
        var j, jl;
        var inputPoints;
        var inputPoint;
        var targets;
        var target;

        if (ig.input.released('tap') || ig.input.released('hold')) {

            inputPoints = ig.input.getInputPoints(['tapped'], [true]) || ig.input.getInputPoints(['holding'], [true]);

            for (i = 0, il = inputPoints.length; i < il; i++) {

                inputPoint = inputPoints[i];
                targets = inputPoint.targets;

                if (targets.length > 0) {

                    // check input targets

                    for (j = 0, jl = targets.length; j < jl; j++) {

                        target = targets[j];

                        // toggle activation

                        target.toggleActivate();

                    }

                }

            }

        }

    },

That doesn't work, but I would have thought it should. Thoughts?

Pattentrick commented 10 years ago

@itsOgden

Just to be clear, what do you expect to happen with your touch events? This for example will work inside your handleInput method at your demo for me:

javascript if ( ig.input.released('tap') || ig.input.state('hold') )


If you tap, the player will throw a color balloon. If you hold, the player will throw color balloons as long as you hold, without any kind of delay. Is this what you want, or do you have something other in mind?

About the explanation, which part is confusing for you? The `handleInput` code may look confusing at first, but basically you have just two loops. The first one will look through all inputPoints and checks if there is a target, and if so the second tries to interact with them. Like calling the `toggleActivate` method of the target in the code above. 

@collinhover

Thanks man! I do my very best ;-)

Thank you for explaining the target and interactive stuff. I noticed that once the parent method in the `handleInput` method registered a event, you can't use that event/inputPoint any more in the child method. Is this because of this?

https://github.com/collinhover/impactplusplus/blob/master/lib/plusplus/core/game.js#L2232
https://github.com/collinhover/impactplusplus/blob/master/lib/plusplus/core/game.js#L2236

And if yes, what does that code?

Also I am quite new to bitwise operators, but ++ is using them in some places. Is that because of performance improvements? And is this a proper way to check for a type with them?

``` javascript```

// entity is a reference to an entity

entity.type & ig.EntityExtended.TYPE.INTERACTIVE

A better mouse handling system would be cool. I'll keep that in mind. But the next thing that I personally would love to do, if I had the time, is to integrate pixi.js into ++. This is a hot topic. Not an easy task I think. I know you had the same idea a few months ago and dropped it. But I can't stop thinking about that. I'll come back to that topic once i finished my customer acquisition and have a project to pay my bills ;-)

itsOgden commented 10 years ago

@Pattentrick, the desire is that it will throw the balloon once the player releases. With just tap, if the player presses down on the lane and then decides he wants to move it to a slightly different place, it no longer recognizes as a tap because now he is holding. So, in that case, when they let go nothing happens. I would like it to throw on the hold release as well as on tap.

Pattentrick commented 10 years ago

@itsOgden Then you could use the touch event like that:

javascript if ( ig.input.released('touch') )



And that event will be triggered once you release the touch, no matter if you just tapped or used a hold. But for whatever reason I can't get an inputPoint with that, if I release the event on a slightly different place, or after a few seconds.

I will have a closer look at the `input.js` of ++ and may figure out why that happens.  
collinhover commented 10 years ago

@Pattentrick to answer your question, it is a very simple method of not allowing clicks to activate multiple targets. Usually you want the target in front to intercept the click. The game's handleInput method is more of an out of the box click handler and example. Calling parent is unnecessary, usually you want to just override it.

You compare bitwise operators using a single & and combine them using a single |. The ops are very fast, and you can check for a single type in many with only one check.

@itsOgden @Pattentrick I think you can also use ig.input.released("hold")

collinhover commented 10 years ago

You can also change some input properties in the config, check out CONFIG.INPUT

itsOgden commented 10 years ago

@collinhover I tried using ig.input.released('hold') and it didn't seem to do anything. I also can't find anything in the config for input; am I somehow missing it?

Pattentrick commented 10 years ago

I think @collinhover actually meant CONFIG.GESTURE ;-)

collinhover commented 10 years ago

@Pattentrick is right, sorry! It does look like hold doesn't get a release, it is only a state, my bad. Does released touch as @Pattentrick suggested work?

Pattentrick commented 10 years ago

@collinhover Nope, in the handleInput method code above the inputPoints won't have a target on release. But I almost have a solution. Just give me a little bit more time ;-)

And thanks for the explanation related to bitwise operators and the handleInput method!

Pattentrick commented 10 years ago

@itsOgden @collinhover Okay … I have a solution that works “most of the time”. And yes, using the words “most of time” gives me the creeps when I code something.

So every idea on this one is highly appreciated ;-)

First of the handleInput method in the main.js:

javascript handleInput: function(){

var inputPoints;
var targets;
var target;

if ( ig.input.released('touch') ) {

    inputPoints = ig.input.inputPoints;

    for ( var i = 0, il = inputPoints.length; i < il; i++ ) {

        targets = inputPoints[i].targets;

        if( targets.length > 0 ){

            for( var j = 0, jl = targets.length; j < jl; j++ ){

                target = targets[j];

                target.activate();

            }

        }

    }

}

}


Also in the `lane.js` change this:

``` javascript```
var inputPoints = ig.input.getInputPoints(['tapped'], [true]);

To this:

javascript var inputPoints = ig.input.inputPoints;



I also noticed a `,` in the `blobShadowSettings` variable that not belongs there (the last one before the closing curly bracket). That has nothing to do with our problem, but the code may break in IE, because of that.

Here comes the “most of the time part”. Sometimes after refreshing the game the inputPoint won't have a target, no matter what. And after a few clicks or seconds everything works as expected. Thats creepy, no idea why that happens :-/
itsOgden commented 10 years ago

Well, as of right now, I haven't had the issue you were referring to. I have no idea what the difference could be, but for now I'm going to pretend the bug is just on your end :D Thanks so much @Pattentrick and @collinhover! You guys are the best.

collinhover commented 10 years ago

It is really all @Pattentrick, he is the man!

collinhover commented 10 years ago

@Pattentrick your solution might be affected by something external, it looks like it should work every time.

Pattentrick commented 10 years ago

@itsOgden Haha - okay then it's just me. Let's pretend that I have never written something about a bug. Glad to hear that everything is working now as it should. Keep us up to date with your progress on your game, looks quite cool.

@collinhover Awwww - thanks for the compliment man! I am just using the stuff that you have created in the first place ;-)

itsOgden commented 10 years ago

@Pattentrick Haha, I'm sure you've never written anything with bugs in it. Never.

The only thing in this vein that I still need to figure out is how to keep clicking one entity from clicking through to the next (example: picking up ammo that is on top of the lane). When they were UI entities they did that naturally, but I haven't been able to replicate the function yet. Still working on it though!

collinhover commented 10 years ago

@itsOgden take a look at how it is done in the parent call: https://github.com/collinhover/impactplusplus/blob/master/lib/plusplus/core/game.js#L2228

itsOgden commented 10 years ago

@collinhover Exactly what I needed. Thanks!

itsOgden commented 10 years ago

You guys up for another issue based on the same issue? I need 1 entity to respond only to a hold release and another to respond to a hold state. Since handleInput() is in main.js, how would I go about customizing these?

I feel so needy these days....

Pattentrick commented 10 years ago

@itsOgden Haha - that's okay. I remember my first Impact++ project a few months ago. I spammed new issues all over the place ;-)

You could place the logic for that in the updateChanges method of the related entity after calling this.parent(). That leaves the problem of the handleInput still triggering that entity in your main.js. You could add a check to that method, maybe looking for a custom name to work around that:

javascript // in the second for loop of the handleInput

if( target.name !== 'mySpecialEntity' ){

target.activate();

}

itsOgden commented 10 years ago

So.... I now have your 0 inputPoint issue happening. I reverted all changes back just to make sure my new stuff wasn't screwing it up, but I think I just didn't notice it before.

If I hold/fire before ever tap/firing, the targets.length is 0. If I tap/fire first, the targets.length is always after that 1+. Super trippy!

I also recently discovered that if I hold and then drag the cursor outside the lanes entity and let go, it will fire out into no-man's land and cause quite a stir lol

collinhover commented 10 years ago

@itsOgden right now there isn't an automated way to handle filtering responses. You need to set up how and when you call the entity's activate method. Doing something like @Pattentrick suggests is fine, but if you have multiple entities and can't use the name, just do an instanceof or type comparison.

As for the other issue, that looks like a bug. Just so I understand you, if you hold before ever tapping, the targets found is always zero?

itsOgden commented 10 years ago

Specifically the targets.length is always equal to zero. I would have thought it would be identical to tapping.

My current method:

    handleInput: function(){

        var inputPoints;
        var targets;
        var target;
        var il;

        if ( ig.input.released('touch') ) {

            inputPoints = ig.input.inputPoints;

            for ( var i = 0, il = inputPoints.length; i < il; i++ ) {

                targets = inputPoints[i].targets;

                if( targets.length > 0  ){

                    for( var j = 0, jl = targets.length; j < jl; j++ ){

                        target = targets[j];

                        if (target.type && ig.EntityExtended.TYPE.INTERACTIVE) {
                            // toggle activation
                            target.toggleActivate();

                            if (il === 1) {

                                ig.input.delayedKeyup.tap = false;

                            }

                            break;

                        }
                    }

                }

            }

        }

    }
Pattentrick commented 10 years ago

@itsOgden Related to the firing into no-man's land: Does this already happens if you leave the lane, but stay inside the actual game? I can reproduce this behavior only, if I leave the canvas with the mouse. If I do understand it right, ++ will trigger a mouseleave/mouseout event if you leave the canvas, which is causing that behavior.

Quick idea for a fix: You could add a check and look if the current mouse/touch position is still inside the canvas. No idea about the other bug though. Let's see what @collinhover has to say about that once he finds the time for debugging.

@collinhover Let me now if you are too busy for this right now. I could try to fix this at the upcoming weekend. I am pretty clueless about the input system in ++ to be honest. This could be a good way to change that ;-)

itsOgden commented 10 years ago

@Pattentrick If I hold down inside the lane, then move the mouse to no-man's land and let go, the reticle spawns there, and the blob and blob shadow kinda go off and do their own thing. It is very similar to the behavior of going off-canvas, but still happens even in-canvas.

Pattentrick commented 10 years ago

@itsOgden To be clear, with no-man's land you mean places like the wall, the area where the player is located, the inventory and so on? That means you just want to throw at things that are inside the lane? If yes, only execute the throwing if the mouse position is inside that lane.

collinhover commented 10 years ago

Sorry guys, been in a crunch so I've no time atm. I'll try to get back to this and fix the bug soon. Otherwise, I agree with @Pattentrick on checking if the mouse is inside the bounds of the lane before spawning anything.

itsOgden commented 10 years ago

Never updated you guys after all of this. So sorry about that! I did end up just checking the mouse position vs the entity position/size. I'm also working around the bug so it hasn't been a problem.