I trying to figure out, how to create a smooth cursor follow animation with cocos2d js
Here some code:
var listener = cc.EventListener.create({
event: cc.EventListener.MOUSE,
onMouseMove: function (event) {
var str = "MousePosition X: " + event.getLocationX() + " Y:" + event.getLocationY();
var actionBy = cc.MoveTo.create(2, cc.p(event.getLocationX(), $scope.bar._position.y));
$scope.bar.stopAllActions();
$scope.bar.runAction(
cc.sequence(
//cc.rotateTo(2, 0),
actionBy
)
);
// do something...
}});
The problem here, its stuck because the event is fired to often and the "$scope.bar.stopAllActions();" stops the animation.
If I remove the "$scope.bar.stopAllActions();" the animation is driving crazy, the "bar" is flying over the screen.
I just wont the bar follow the mouse cursor, just like the dog follow human
The problem here is that if you remove stopAllActions(); you'd be trying to run an action on an object that's already got an action of the same time. And in either case you are firing an animation too often on an object, I've rarely seen this work as expected.
I'd try running the action directly instead of putting it within a cc.Sequence first, but if that doesn't work, you've got two choices, both rely on manual labor:
a. Simply put the bar on the position of the mouse whenever it moves:
$scope.bar.x = event.getLocationX();
b. Assuming you want to "juice up" the game, just setting the bar on the mouse will be boring, so you could make the bar progressively catch up with the mouse:
In your constant section:
var EASING_CONSTANT = 0.9;
In the event handler:
$scope.bar.x += ($scope.bar.x - event.getLocationX()) * EASING_CONSTANT;
The lower your EASING_CONSTANT the slower the bar'll catch up to the mouse (only use values between 0 and 1).
Now, if you try to use this, you'll realize the bar never quite catches up with your mouse, so you'll have to place this code within an update function (in your main game layer's update method, for example) so it runs each frame.
BUT! Then you won't have access to an event object, so you'll have to end up with something like this:
In the event handler:
$scope.bar.targetX = event.getLocationX();
In the update method:
$scope.bar.x += ($scope.bar.x - $scope.bar.targetX) * EASING_CONSTANT;
There are lots of easing functions you can use, I just gave you the simplest example.
For instance, note that you also have the event.getDeltaX() method that'll return you the difference in position between the last call of the event and the current (how much the mouse moved since the last call of the event). With this you could do something along the lines of:
In your constant section:
var EASING_CONSTANT = 0.9;
var WOBBLING_CONSTANT = 10;
In the event handler:
$scope.bar.targetX = event.getLocationX();
$scope.bar.mouseDeltaX = event.getDeltaX();
In the update method:
$scope.bar.x += ($scope.bar.x - $scope.bar.targetX) * EASING_CONSTANT + Math.cos($scope.bar.mouseDeltaX) * WOBBLING_CONSTANT;
Just get creative and start messing around with the formula until you find a behaviour that "feels" right. But be sure to start simple first!
PS: I'm guessing you want your "bar" to only move on the x-axis, so.. are you making an arkanoid clone? :D
Related
So I'm using Three.js and I have some cubes inside of a box. I'm using the Transform Control to move the cubes around inside of the box with my mouse. I'd like to use raycasting in order to check for collisions. The question is how to I prevent the transform controller from moving the object if there is a collision? I'd like to stop it if it hits the wall. By the way, I'm on version r81 for Three.js.
UPDATE: I've used the size of the room to constrain the cubes from
moving outside of the room. This seems to work well. Is there a way
to use the cannon.js just for collisions? I don't want the momentum
or gravity or any other feature. JUST the collision check and to stop
it dead in its tracks when there is a collision.
I know this post is from a long time ago, but hopefully a googler finds this helpful. I wasn't able to stop the user from moving my object, but I was able to move it back to its proper position immediately afterward by adding some logic to the render method.
For the original poster's problem with collisions, you could attach an event listener to the transform controls and request the object to be repositioned if it is in an illegal state.
transformControls.addEventListener('objectChange', (e) => {
if (illegalPosition(this.obj.position)) {
needsReset = true;
}
lastPosition = attachedObject.position.clone();
});
and then in your render function
if (needsReset) {
attachedObject.position.set(lastPosition.x, lastPosition.y, lastPosition.z);
}
If this feels a little hacky, that's because it is. But for those of us who don't have the time or skill to read and modify TransformControls.js, I think it may prove helpful.
You could create helper raycaster and place all colliders in separate container. After movement is applied to object move raycaster to its position and test if ray intersects any of other objects in container. If yes: reset previous position for that object. In case of cube colliders you could want to raycast from cube center in multiple directions with half of side length as ray length.
Ben S does have the best and most painless way to implement collision detection with transform controls. Within a event listener.
But I don't know if the time of writing his answer he knew about or if there even was a function called "requestAnimationFrame". All you would have to do for collision detection instead of simply resetting the models position is to set up your render call within a loop (60 fps) by adding "requestAnimationFrame" to your render (I call it animate since that is more descriptive) function.
Since it is in a loop and is called when the every frame the scene is drawn it will just not allow the object to move past the point of collision.
function animate() {
// Called to draw onto screen every frame (60fps).
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
And your event listener would just look like this.
control.addEventListener('objectChange', (e) => {
// Collision detection code here. Set colliding model position here.
// No need to set it in render
});
Old post, I know. But here is a method that is still fairly simple but does not flicker or use ray casting. The biggest catch here is that you have a little bit of a bounce if you move the Transform control really quickly. But otherwise it seems to work fairly well. You can control the precision of the collision by adjusting the step value.
let transStart = null;
//capture objects position on start
control.addEventListener('mouseDown', function(){
transStart = control.object.position.clone();
})
//you'll have to provide your own collision function
control.addEventListener('objectChange', function(e){
if(collision(sphere, cube)){ stopControls() };
});
function stopControls(){
if(control.dragging && stopAt){
//calculate direction object was moving at time of collision
const s = transStart;
const e = control.object.position.clone();
const n = e.clone().sub(s).negate().normalize();
//janky hack nonsense that stops the transform control from
//continuing without making the camera controller go nuts.
control.pointerUp({button:0});
control.dragging = true;
//translate back the direction it came by the step amount and do not
//stop until the objects are no longer colliding.
//Increase the step size if you do not need super precise collision
//detection. It will save calculations.
let step = 0.00005;
while(colliding(sphere, cube)){
sphere.translateOnAxis( n, step ) ;
sphere.updateMatrix();
}
}
}
My actual use case is to draw something (not html, but render on the canvas) in response to mousemove events - it all actually boils down to nothing important to do unless the mouse changes positions.
I am seeing that requestAnimationFrame can be thought of as a way to throttle user input such as mousemove and I understand why it should be that way and why it is a code smell to use it that way if mousemove -> draw is not the actual purpose of the code.
Is there anything necessarily wrong with calling requestAnimationFrame from within the mousemove handler?
If I do not call requestAnimationFrame from the mousemove handler and the drawing rate is actually slower than the user input loop (the preferred rate or however it works), do I still get the desired throttling in that case?
Firstly, the information provided in this question was very informative.
Second, given that mousemove events can possibly fire much more often than rAF, it would seem that calling rAF from the mousemove event is a bad idea, despite the fact that it can be throttled as suggested in the above question.
For the purpose of rendering in response to mouse movement, it seems that a better approach is to let mousemove fire the way it wants to and DO NOT try to call rAF from there. If the mousemove event is trying to fire at a ridiculous 1000Hz as suggested in the above question, the only thing the handler needs to do is update a couple of shared mouse position variables and set a shared flag if either mouseX or mouseY have changed.
Likewise, set up a standard rAF loop and let it run at it's preferred rate, while from the rAF callback, check the flag to see if the mouse has moved, if it has then there will be something to render, if not, then do nothing.
This way the two processes are not trying to control each other and the code is cleaner and makes more sense.
This will show you the difference of mousemove and an animation:
var doc = document, c = doc.getElementById('canvasId'), d = c.getContext('2d'), b = c.getBoundingClientRect();
function logTime(title){
var dt = new Date;
console.log(title+': '+dt.toLocaleDateString()+' '+dt.toTimeString().replace(/\s.*/, '')+'.'+dt.getMilliseconds());
}
function draw(e){
logTime('mousemove');
requestAnimationFrame(function(){
logTime('requestAnimationFrame');
});
var x = e.clientX - b.left, y = e.clientY - b.top;
d.fillRect(x, y, 1, 1);
}
c.addEventListener('mousedown', function(e){
draw(e); c.addEventListener('mousemove', draw);
});
function stop(){
c.removeEventListener('mousemove', draw);
}
doc.addEventListener('mouseup', stop);
It's also firing with a 'mousemove' title on mousedown, but other than that it logs times you can look at to determine what you need to know. Although this code does not demonstrate it you can use requestAnimationFrame to hold out on the execution of code until the animation is complete to do something else.
I have an image of a bug. I want to make 5 copies of that image fly in from the side of the screen and bounce around the screen and bounce off the sides. I want them to all have different starting positions and different directions.
so I made some a global variables
var flyVar;
var flySpeed = 5;
var widthMax = 0;
var heightMax = 0;
var xPosition = 0;
var yPosition = 0;
var xDirection = "";
var yDirection = "";
var bugFly;
var count = 1;
var bug = "bug";
I have a function called setBugs() that I use to set the value of widthMax and heightMax depending on the size of the users screen.
I have a bugStartingPlace function to set the initial starting place for each bug. I won't post the whole function but it does the same for "bug1" through "bug5", giving them different values.
function bugStartingPlace(bugName) {
//Accepts string as argument and sets the starting position and starting direction of each bug.
if (bugName == "bug1") {
xPosition = 0;
yPosition = 100;
xDirection = "right";
yDirection = "up";
}
}
I have a function called flyBug() that does the animation and sets the position of the image. It consists of a bunch of statements like this. I know it works because I can make it work with 1 bug. The problem is doing it with 5 bugs.
function flyBug() {
if (xDirection == "right" && xPosition > (widthMax - document.getElementById("bugImage").width - flySpeed))
xDirection = "left";
<!--More flow control statements are here-->
document.getElementById("bug1").style.left = xPosition + "px";
document.getElementById("bug1").style.top = yPosition + "px";
<!-- More statements are here that set the position of the image -->
}
So, I need some way to get the animation going with the body onload() event. One problem is that setInterval does not allow functions that contain parameters. So I can't put multiple statements in the body onload event that pass "bug1" as a parameter to this function, "bug2" as a parameter to this function and so on. That's why I made the global count variable. That way, any time I need to change the name of the bug, I change the name of count and then do
bug = bug + count;
But that adds a lot of complexity. I need the name of the bug for the bugStartingPlace() function, so I need to change the value of count and also change the value of bug before I use that function. Once I use the bugStartingPlace() function, that changes the value of the global variables. Then I need to use flyBug() before I change the value of bug again.
I guess one of the problems is that I'm using global variables for direction and position even though I have multiple bugs. It works fine for one bug but not for multiple bugs.
Can anyone give me tips on how the logic of this program should work?
setInterval allows, like setTimeout, the use of parameters in the function BUT:
setInterval(funcName(param1,param2...), 100);
wont work. Youll get it to work like that:
var func = function () { funcName(param1,param2..); }
setInterval(func, 100);
To understand that part of javascript, read through dougles crockfords explanation of functions, he tells about this very clear and deep. Link to a video of him
EDIT: Sry i understood your question wrong...
The problem why it wont work is, like you figured out the global vars. You could just make bug an object. His actions will then be methods, which can contain a function and so on. If you then initialize a new bug (you can do this a thousand times then), all the vars stay in the object, without conflicting each other. This is a secure way to provide solidness of your code.
You could do it very simple, with nested functions.
Another way would be, to send the name of the bug vie parameter to the, for example, fly function. And the only work in that function with the parameter given to it.
Here is the problem, I've got a tree structure of html blocks, global container is of a fixed width(X) and height(Y). When i click one of the blocks on a level, all other blocks shrink to some size, while the clicked one gets enlarged to the leftover space, and the sublevels show up on it's place.
For all the shrinking i'm using default animate function with easing effect, when shrinking 1 level, to avoid enlargement bugs i have to do something like this:
$tabs.not($obj).animate({height:32<<$obj.getVerUp().length+"px"},{duration:300,
step:function() {
$obj.height(function(){
var sum = 0;
$tabs.not($obj).each(function(){
sum += $(this).height();
});
return $obj.getCont().height()-sum+"px";
});
}
});
$tabs are all the tabs of current level, $obj - is the one tab that i want to enlarge
The main problem is:
When i open up a tab that is on a deep level, i have to animate all the tabs of higher levels to shrink a little bit more, thus the $obj X and Y would change, so the current animation has to use new values, but if i call 3 different animations on different levels i'm bound to get a bug, when one of the animations on a deeper level finishes 1 step earlier, while the one on the level above, would enlarge the object by 5-10 more pixels and that space wouldn't be used up.
The second problem is that there has to be about 50 object animating with easing at the same time, which is a little bit overkill.
And the last problem is when i call step callback on animation as shown above, i have a strange feeling that it calls the step separately for each animation of the $tabs collection, while i need 1 step for all the tabs in the list (to avoid unnecessary scripts)
There might be some other way to fix all that, but i have yet to discover all jQuery functions, so from what i see the only way is to simulate easing, and do everything in one single animation.
I don't really want to use setInterval and determining when do i need to clear it plus calculating each of the easing values, if there is a simple way doing it.
Does jQuery has some sort of empty animation easing, e.g.
$().css("height":starth+"px").animate({height:endh},{duration:300,
step:function(fn) {
// all the animation actions here via fn end value
}
});
Thanks in advance.
What I need - is not a completely working solution in code, just some enlightenment in those subjects:
Is there a legal way to call one step function for a collection of animated elements, or, maybe, it does call step once when I use one .animate on collection.
I'd be really appreciated if someone would shed some light over how does jquery handle multiple .animate, would they be used in one global function that works on .setInterval? or would they be having massive number of those .setIntervals that are equivalent to setTimeout (which most browsers can't handle in large amounts);
Is there a way to simulate 'animate' easing, some function name maybe, or a special trick to achieve that (the only thing I see is a hidden element or 'window' property to change maybe)
Or some directed pushes with functions I should study, that could help me achieve my goals
Guess i pretty much found the answer to my questions:
http://james.padolsey.com/javascript/fun-with-jquerys-animate/
Here's the empty animation from the link above with 1 step function with desired values, going to post the result later on if it all works out.
var from = {property: 0};
var to = {property: 100};
jQuery(from).animate(to, {
duration: 100,
step: function() {
console.log( 'Currently # ' + this.property );
}
});
Yes it all worked great, no desynch, and a good speed, since only 1 animate, found making one universal function for the animation - waste of resourses, so it is pretty specific, but still, here it is:
animate: function($obj) {
var T = this;
...
T.arr = new Array();
// gathering the array
$obj.each(function(i){
var size;
T.arr[i] = {obj:$(this), rest:$(this).getSibl(), cont:$(this).getCont()}
if($(this).hasClass("vert"))
{
size = "height";
T.arr[i].to = yto;
}
else
{
size = "width";
T.arr[i].to = xto;
T.arr[i].children = $(this).getChld();
}
T.arr[i].rest.each(function(){
$(this).attr("from",$(this)[size]());
});
});
// animating prop
jQuery({prop:0}).animate({prop:1}, {
duration: 300,
step: function() {
var i;
var P = this;
var newval;
var sum;
var size;
for(i = 0; i < T.arr.length; i++)
{
size = T.arr[i].obj.hasClass("vert") ? "height":"width";
sum = 0;
T.arr[i].rest.each(function(){
// new value of width/height, determined by the animation percentage
newval = parseInt($(this).attr("from")) + (T.arr[i].to-$(this).attr("from"))*P.prop;
$(this)[size](newval);
sum += newval;
});
T.arr[i].obj[size](T.arr[i].cont[size]()-sum);
}
}
});
},
I'm working on a Javascript/jQuery calendar which includes a month view and a day view. Clicking the days will change the date, which will update the date variables in the day view.
The day view is split up into half hour segments from midnight to 11:00 PM. Clicking on any half hour <tr> (the day view is a table) will create an event between that time clicked and an hour in the future, as well as append a div on top of the calendar, spanning the range of time and positioned at the correct starting point (each pixel is a minute...)
There is a problem, however. If you create an "event" between a certain time span where there is already one in place, they overlap. This is the default behavior, obviously, but what I would like to happen is that if an event is created between a range of dates that is already occupied by an event, they align side by side so that they're not overlapping.
This resembles the behavior seen in the iCal app for mac:
Now my first thought to achieve such a goal was to use collision detection, but all the jQuery plugins for this are bloated or require the elements to be draggable.
Then I thought there might be a way in CSS to do this, where if two elements are overlapping, they split the width evenly.
Then I thought that's ridiculously far fetched, so I'm wondering how I can achieve this as easily as possible.
I'll post the full code in a jsFiddle, but for the most important function would be insertEvent which looks like this:
function insertEvent(start, end){
var end_minutes = new Date(end).getMinutes();
var end_border = new Date(new Date(end).setMinutes(end_minutes + 2));
//$(".day_date").html(start + "<br />" + end);
var diff = Math.abs(end_border - new Date(start));
var minutes = Math.floor((diff/1000)/60);
var start_element = $("td").find("[data-date='" + start + "']");
var offset = start_element.offset().top - $(".second").offset().top;
var this_element = $("<div class='event' style='height:" + minutes + "px;margin-top:" + offset + "px;'></div>");
$(".right").prepend(this_element);
}
This takes two parameters in the javascript new Date() format, one for the start date and one for the end date.
The fiddle: http://jsfiddle.net/charlescarver/HwdwL/
One of the the problems I see with your approach is that there isn't a structure to the storage of the data. I've built a calendar in Javascript before and it's not easy work. First, make sure you have some kind of abstraction for the calendar event. Something like:
function CalendarEvent(startDateTime, endDateTime) {
this.startDateTime = startDateTime;
this.endDateTime = endDateTime;
}
CalendarEvent.prototype.start = function() {
return this.startDateTime.getTime();
};
CalendarEvent.prototype.end = function() {
return this.endDateTime.getTime();
};
CalendarEvent.new = function(startDateTime, endDateTime) {
// This is a little factory method. It prevents calendar events
// from having end times that fall before the start time.
// USE THIS TO INSTANTIATE A NEW CALENDAR EVENT
if(endDateTime.getTime() < startDateTime.getTime()) {
throw new Error("End time falls before start time");
}
return new CalendarEvent(startDateTime, endDateTime);
};
CalendarEvent.compare = function(eventOne, eventTwo) {
// this is a class method to compare two events
// If used with sort it will sort by startDateTime
return eventOne.start() - eventTwo.start();
};
// ... add any other methods you need
Next you're going to want to sort the calendar events. I would sort by start time. Then once it is sorted you can actually re-render everything when changes are made. As long as you sort correctly, determining if a calendar event collides is as simple as this:
CalendarEvent.prototype.intersects = function(otherEvent) {
// If the other event starts after this one ends
// then they don't intersect
if(otherEvent.start() > this.end()) {
return false;
}
// If the other event ends before this one starts
// then they don't intersect
if(otherEvent.end() < this.start()) {
return false;
}
// Everything else is true
return true;
};
Because the data is sorted you know that if two or more calendar events intersect they will have to share the space. Granted, you must think about a few things when you divide the space. Do you want a naive implementation where you just share the space equally from left to right (left having the earliest start time). If so your visual representation could look like this if it had 4 events that shared a space (each block is an event):
However if your events have strange shapes they might cause your calendar to look strange. Consider the following:
In this instance event 2 takes up a lot of vertical space and all the space underneath event 1 is unused. Maybe for a better UX you don't want that kind of thing to happen. If so you should design your rendering algorithm accordingly. Just remember that it is probably easiest to re-render on every change that you encounter, but it's all about how you store the data. If you do not store the data in some kind of structure that is easily traversed then you won't be able to do this kind of thing.
But to complete the answer to your question, here is a fairly naive example. I haven't tested it so this is a pretty big assumption of it working. It is not entirely complete you will have to edit the rendering for yourself. This is merely to give you an idea of how to get it to work. It could definitely look prettier:
function renderCalendarEvents(calendarEvents) {
// Sort the calendar events (assuming calendarEvents is an array)
var sortedEvents = calendarEvents.sort(CalendarEvent.compare);
var index = 0;
// renderEvents is an anonymous function that will be called every time
// you need to render an event
// it returns it's columnDivisor.
var renderEvent = function(position) {
var currentEvent = sortedEvents[index];
var nextEvent = sortedEvents[index + 1];
// The default column divisor is determined by
// the current x-position + 1
var columnDivisor = position + 1;
// Increment before any recursion
index += 1;
// Check if nextEvent even exists
if(nextEvent) {
// If the nextEvent intersects with the current event
// then recurse
if(currentEvent.intersects(nextEvent)) {
// We need to tell the next event that it starts at the
// column position that is immediately +1 to the current event
columnDivisor = renderEvent(position + 1);
}
}
// placeEvent() is some function you can call to actually place
// the calendar event element on the page
// The position is the x-position of the current event
// The columnDivisor is a count of the amount of events sharing this column
placeEvent(currentEvent, position, columnDivisor);
return columnDivisor;
};
while(true) {
// render events until we're done
renderEvent(0);
if(index >= sortedEvents.length) {
break;
}
}
}
Essentially the idea with this particular algorithm is that if the nextEvent on the list exists and that event intersects with the currentEvent then we need to split the width of the currentEvent. It keeps on recursing until it finds no more intersections then it makes it's way back up the chain of recursive calls. I skipped the actual DOM manipulation logic because really the hard part is determining how much you need to split the actual column in order to get these events to fit. So hopefully this all makes a little bit of sense.
EDIT:
To be much more clear, in order to add this to your existing code I would replace your insertEvent function with something like this. I don't write all of the logic for you so you'll have to do some of your own writing. But that's half the fun :-).
function insertEvent(start, end) {
var newEvent = Calendar.new(start, end);
// you'll have to store the array somewhere.
// i'm just assuming some kind of global right now
eventsArray.push(newEvent);
// You'll want to destroy any event elements
destroyCurrentEventElements();
// Now run the rendering function
renderCalendarEvents(eventsArray);
}