Jump to content

Point and click movement ?


Duckydoc
 Share

Recommended Posts

Hello everyone,

I've been trying to learn HTML5 over the past few weeks. Player movement when using the arrow keys was pretty easy to make. 

Point and click movement is an other thing.. For now I've only been trying with the X axis and I'm struggling already.

I want my character (a simple rectangle) to slowly move where I'm clicking on the canvas.
The only results I got were the rectangle immediately jumping to the X coordinate I clicked or only moving once after clicking.

My code is the following :

event listener :

canvas.addEventListener('click', onClick, false);

function onClick(event)
{
	targetX = event.clientX - canvas.offsetLeft;
	fromX = hero.x; 
	distanceX = targetX - fromX;

	startTime = new Date().getTime();

	hero.move(distanceX);
}

move function inside hero's function

this.move = function(distanceX)
	{
		travelTime = distanceX * 400;

		while (time < travelTime) {
			this.x += 1;
		}
	}

update & draw functions 

function update()
{
	draw();
	time = new Date().getTime() - startTime;
	requestAnimationFrame(update);
}

function draw()
{
	context.clearRect(0,0,canvas.width,canvas.height);
	context.strokeText('Time : ' + time, 32,32);
	hero.draw();
}

 

Every time I click somewhere on the canvas, time resets to 0.

For some reason, while (time < travelTime) makes my game crash.
And if that replace that with a if statement, my rectangle only moves 1 pixel then stops.

Any help would be appreciated, I'm kinda lost.

Thank you 

Link to comment
Share on other sites

You're super close. Welcome to the wonderful world of asynchronicity (spoiler alert: its tricky!).

function (distanceX) {
  travelTime = distanceX * 400;

  while (time < travelTime) {
    this.x += 1;
  }
}

Step back a minute and think about what is happening with this loop.

A while loop is a blocking construct, this means it blocks the thread JS executes in until it is done.

That sentence contains 2 key bits:

* Blocks the thread

* Until it is done

Tackle the 2nd problem first. 

Until it is done.

This loop does only one thing, it increments a variable. However, the conditional for the loop `time < travelTime` isn't the variable that is incrementing.

In JS you could actually apply a side effect by using a setter such that whenever you apply a value to `this.x` something else happens which changes `time` or `distanceX` (as `travelTime` is calculated from `distanceX` a change in `distanceX` is actually the important change), however, I very much doubt you're doing this and nor should you, it's powerful for sure, it's also unexpected and confusing and best avoided.

The reason the thread crashes is linked to both of the 2 things listed above, namely, time never exceeds travelTime so the `while` loop keeps on looping forever. Well, not forever, but until the JS thread crashes (exactly why it crashes I'm not sure, eventually you'd overflow the max value the `this.x` variable can accept, which is a big number, very big, but not infinite, even if it could be infinite you'd run out of memory, which means it isn't infinite, but, I think the JS thread knows it is borked after a set amount of time and crashes. This is all academic, it does crash).

So, first things first, sort out that function so that the conditional eventually returns false. However, this is only part of the problem, and that is connected to issue 1 above.

While is blocking

JS is single-threaded, i.e. it'll only run one sequence of calculations (there are, sort of, ways around this with web workers, but, ignore that for now, threading is proper tricky and its best avoided, hence why JS doesn't entertain the idea). A while loop will continue to insert itself to the top of the call stack of operations i.e. it will keep running until it is done. Consider the following:

var iterator = 10

while (iterator > 0) {
  console.log('Hello')
  --iterator
}

console.log('World')

This hopefully works as you would expect i.e. it prints `Hello` ten times, then `World` (JS trivia, for some reason while loops are fastest decrementing not incrementing, you don't need the > 0 as 0 is falsy, does it print 10 or 9 times?, I'll let you play with the prefix -- operator, try changing it to postfix, you can also whack that in the conditional, clue, `while (--iterator)` is not the same as `while (iterator--)`).

The while loop blocks the thread, `World` is only printed when it is done.

Now extrapolate that out to what you actually want to happen, i.e.

var iterator = 100

while (--iterator) {
  this.x = iterator
  this.render()
}

This looks like we are getting somewhere, but, its a bit of a red herring.

This will render 100 times, and, the `x` value will decrease with each render.

However, it'll happen as fast as possible, which is probably way too fast. (I'm not even sure if it'll get optimised away, so it won't actually render 100 times).

What you probably want is for your sprite to go from current position to desired position in a set amount of time, or, at a set speed. Either is achievable.

However, this is asynchronous, so here be dragons.

For starters:

* Ditch the while loop, it isn't what you want here.

I'd probably approach this by structuring things ever so slightly differently and letting your update function do a bit more work.

Let's assume you want to move based on a constant speed (let's ignore anything such as speeding up or slowing down, although, its a good extension exercise, as is doing this by time, but I doubt that's what you actually want here). edit: looks like you do want to do it based on time, the outline below wouldn't really change, your problem is handling asynchronicity, not the actual logic of what is happening over time.

We'll need a couple more variables, some you already have modelled (I'll keep it in one plane for brevity):

var speed = 1
var currentX = 100
var desiredX = 10

A click sets the desiredX, you're already doing this.

Now add a little bit of logic to the update function (again, lots of ways you could achieve this):

function update () {
  if (currentX === desiredX) {
    return
  }

  if (currentX < desiredX) {
    currentX = currentX + speed
  }

  if (currentX > desiredX) {
    currentX = currentX - speed
  }
}

You might want to work out what speed you want somewhere else. You have a move function that is called from the click user action, maybe there would be better. Doesn't much matter. There are also way better, but slightly more complex, ways too. Also, in this example we are checking that desired and current are equal, as we're only dealing with integers here and incrementing or decrementing by 1 we know we'll always hit the desired, if we use floats or any value besides 1 then we can't be sure they'll ever be equal so your 'bug-out' conditional would need to be a bit smarter. Google for shield pattern, it can be useful here.

You're already calling update (and draw) on a period, namely every 16ms or so, or, more accurately, ~16ms or slower.

Rather than update the position all in one go (via the while), you're now letting your update function actually update your world by taking the difference  of current state vs desired state and applying the changes required to get to the desired state over time. The over time bit means you are now doing it asynchronously and you'll get the movement you probably expect. In this example it'll take you 90 frames to hit your desired state, or, roughly 90 * 16 = 1440ms to get to the mouse click position.

Once you get your head around making changes over time then the world is your oyster.

Want to get there in a set time frame? Choose a speed based on the time left vs the distance left.

Want to apply some rudimentary physics? Start modelling forces i.e. apply a force to the object and calculate speed based on the forces acting on the object (this route is awesome, clue, slowing down can be tricky).

Good news is that all of these things have mathematical formulae that you can model in to your simulation, and its mega easy to find them with a quick google search.

The other good news is that this approach is actually way simpler. If a user clicks again in the 1440ms it takes you to 'complete' this move then you just change the `desiredX` and everything just works. The mental shift is that a click does not equal a 'move', it sets a destination, the update function handles getting from one destination to another, it has no concept of moves or times, it just deals with the here and now of 'I want to be over there, how do I get there?', if the 'over there' changes, the previous question still remains the same, the answer will change, but you calculate that in the update i.e the code is the question, the output the answer, changing one is significantly easier than changing the other.

Then you can deal with path-finding when you realise that in between your current position and the desired position is a wall/chasm/enemy/etc.

Link to comment
Share on other sites

Thank you, very instructive post, which I'm sure will help more than one when they will be googling this.

I had done a similar thing, and you make me realise my mistake was to not call the "move" function in the update function. From there, it all gets easier.

Now it's time to work on collision ! Thanks again.

Link to comment
Share on other sites

On 11/14/2018 at 10:59 PM, mattstyles said:

Start modelling forces i.e. apply a force to the object and calculate speed based on the forces acting on the object (this route is awesome, clue, slowing down can be tricky).

I said calculate speed, doh, you don't do that. Speed is a observable of movement over time. Writing things correctly is hard.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...