Game Development Community

Release: More mouse problems

by Vern Jensen · in Torque Game Builder · 06/21/2006 (4:09 pm) · 11 replies

First, let me explain what I'm trying to do. I'm building a space game, where the player's ship is located in the bottom-center of the screen, and doesn't move, but rather rotates when you move the mouse left or right. If the mouse is in the center of the screen, the ship points up, if the mouse is at the far left of the window, the ship points -90 degrees, and if the mouse is at the far right of the window, the ship points 90 degrees.

The problem is I'd like to support windowed mode, in addition to full-screen. This means the user could potentially move the mouse outside the window. There are two problems with that:
1) When they click to shoot, they might deactivate the game (if the mouse was moved outside the window, they could click on the desktop.)

2) When the mouse is moved outside the window, my onMouseMove() and onMouseDragged() callbacks don't get called anymore. If you move the mouse outside of the window fast enough, the ship won't even have rotated to the full 90 degrees, and will stay stuck in that position until the mouse re-enters the window.

Now onto the bugs:

1) As mentioned in a previous post, calling

sceneWindow2D.setLockMouse(true);

doesn't help. My onMouseMove() and onMouseDragged() callbacks still aren't called when the mouse is outside the window. But even this solution wouldn't solve the problem of the user clicking when outside the window, so we must try another solution:

2) The concept is simple: at the start of the level, center the mouse in the window by calling sceneWindow2D.setMousePosition(). Then inside the onMouseMove() callback, compare the mouse's X location set the that callback with the center of the screen. This is the X delta the mouse has moved. Then re-center the mouse by calling sceneWindow2D.setMousePosition() again.

This would solve my problem because the mouse can never get anywhere near the edge of the window, let alone move outside of it. But I can still tell how it was moved, and rotate my ship accordingly. In theory.

In practice, setMousePosition() has problems. Yes, it does relocate the mouse, but (at least on MacOS X 10.4) there is a delay associated with this, so that after it is moved, it won't respond to user movement until about half a second later. This result in mouse movement being "lost" during this delay, and a resulting very chopping movement of the ship that is completely unacceptable for a game.

So currently, I am left with only one solution: run the game full-screen. I am hoping one or both of these problems in the engine can be fixed at some point, so that I can later add windowed-mode back to my game.

#1
06/21/2006 (4:19 pm)
Okay, here is some code that also demonstrates what I'm experiencing. You could paste this into player.cs of the shooter tutorial to see it in action (replace or comment out the existing code).
function playerShip::onLevelLoaded(%this, %scenegraph) 
{ 
   $pShip = %this;
	
	moveMap.bindCmd(keyboard, "escape", "escapeKey();", ""); 
	
	%theRect = sceneWindow2D.getCurrentCameraArea();
	$worldRectLeft = getWord(%theRect, 0);
	$worldRectRight = getWord(%theRect, 2);
	$worldRectWidth = $worldRectRight - $worldRectLeft;
	$worldRectMid = $worldRectLeft + $worldRectWidth/2;
	
		// Center the mouse in the window, but don't change its Y coordinate
	%mouseLoc = sceneWindow2D.getMousePosition();
	$mouseLocY = getWord(%mouseLoc, 1);
	$mouseLocX = $worldRectMid;
	$oldMouseLocX = $mouseLocX;
	sceneWindow2D.setMousePosition($mouseLocX, $mouseLocY);
}

function sceneWindow2D::onMouseDown( %this, %modifier, %worldPos, %mouseClicks )
{
	if ($paused)
		return;
	
//	pShipFire();
} 

function sceneWindow2D::onMouseUp( %this, %modifier, %worldPos, %mouseClicks )
{
//	pShipFireStop();
} 

function sceneWindow2D::onMouseDragged( %this, %modifier, %worldPos, %mouseClicks ) 
{
	%this.onMouseMove( %modifier, %worldPos, %mouseClicks );
}

function sceneWindow2D::onMouseMove( %this, %modifier, %worldPos, %mouseClicks ) 
{ 
	if ($paused)
		return;
		
	%xLoc = getWord(%worldPos, 0);
	%yLoc = getWord(%worldPos, 1);
	
	if (%xLoc == $worldRectMid)
		return;
		
	echo(%xLoc SPC $worldRectMid);
	
	%offsetX = %xLoc - $oldMouseXLoc;
	$oldMouseXLoc = %xLoc;

	if ($worldRectMid - %xLoc > 30 || %xLoc - $worldRectMid > 30)
	{
		sceneWindow2D.setMousePosition($worldRectMid, %yLoc);
		$oldMouseXLoc = $worldRectMid;
	}
	
	$mouseLocX += %offsetX;
	
	if ($mouseLocX < $worldRectLeft)
		$mouseLocX = $worldRectLeft;
	if ($mouseLocX > $worldRectRight)
		$mouseLocX = $worldRectRight;
	
//	%xLoc = getWord(%worldPos, 0);
	
	%percent = ($mouseLocX - $worldRectLeft) / $worldRectWidth; // get value between 0 and 1
	%rotation = %percent * 180 - 90;

	$pShip.setRotation(%rotation);
}


function escapeKey()
{
	if ($paused)
	{
		Canvas.hideCursor();
		mySceneGraph.setScenePause(false);
		$paused = false;
	}
	else
	{
		Canvas.showCursor();
		mySceneGraph.setScenePause(true);
		pShipFireStop();
		$paused = true;
	}
}

Move the mouse left/right just a little bit. Notice that the ship's rotation is smooth? Now continuously move the mouse left or right. Notice the big jolts in the ship's rotation? That's because this version re-centers the mouse only when it gets a distance of 10 from the center of the screen. My previous version, which re-centered it EVERY time the onMouseMove() callback was called, failed to work at all. (Again, something buggy in the engine.)

The point is, every time the mouse is re-centered, there is an ugly pause, and any mouse movement that happens during this pause is lost.

This problem means that any game that wants to allow the mouse to control something *indirectly* will fail to work. (That is, the object the mouse is controlling is not directly related to the mouse's *location* but rather related to the mouse's movement.)
#2
06/21/2006 (4:46 pm)
That sounds to me like a very strange way to do it, not that I know much =D

Is there a way to restrict the mouse movement? I think there is but I don't know how. Then what you want to do is onmousemove test if it is on a border pixel--the last pixel on one side of the window. Is so, lock further movement in that direction.
#3
06/21/2006 (5:09 pm)
It may seem strange, but I know it's a common technique for games where the mouse *movement* controls something in the game, rather than the mouse *position* controlling it. (For Mac users out there, remember Crystal Quest? That would be one example of this type of feature being *necessary* to control the main character in the game. Your mouse's movement actually affected the velocity, not position, of the main ship. Move the mouse left more, your velocity gets faster and faster, etc.)

As for my game, I could get by with a method of just restricting mouse movement to stay within the window, if such a thing were possible, but it doesn't seem to be in TGB. Take for example this function I just created, which *should* restrict mouse movement:
function sceneWindow2D::onMouseLeave( %this, %modifier, %worldPos, %mouseClicks ) 
{
	if ($paused)
		return;
	
	%xLoc = getWord(%worldPos, 0);
	%yLoc = getWord(%worldPos, 1);
	
	if (%xLoc < $worldRectLeft)
		%xLoc = $worldRectLeft;
	if (%xLoc > $worldRectRight)
		%xLoc = $worldRectRight;

	sceneWindow2D.setMousePosition(%xLoc, $worldRectMidY);
}

However, it has the same problem I've mentioned. That is, a delay of half a second or so most of the times the mouse moves outside the window. And all mouse movement that happens during this delay gets "lost" -- it's not reported to any callbacks. This causes unacceptable choppiness in the game whenever this function is called. Otherwise, the mouse *is* contained nicely in the window. So the delay is the only problem here.

Any method I try, I can't get what I want in TGB at the moment. Not in windowed mode at least.

-Vern
#4
06/22/2006 (2:54 am)
We had the same problem with scenewindow2D.setLockMouse(true). but if you instead call lockMouse(true) (see post: ) at the start of the game (we just called it in game.cs in startgame(%level)) it works. it doesnt have the delay setLockMouse has.

we also reset the mouse to the center every time it gets dragged but because there are still some extreme values the movement wasnt smooth. so we just took an average of every fifth movement and that works fine.

maybe that helps.
#5
06/22/2006 (3:56 am)
Hi.
I think that you should get better result using the ".bind" method instead of the mouse callbacks.

Put the line:
moveMap.bind(mouse0, "xaxis", RotateShip);
into the ::onLevelLoaded of your playership callback, then create the "RotateShip" function:
function RotateShip(%val)
{
      // rotate the ship here
}

The %val parameter should tell you how much the mouse moved on the xaxis, since the last callback.
I can't test this since I'm at work, but this should work.
Let me know if it helps you.

BTW, you will find some useful info about the code posted here in the "Input interaction.pdf" reference file, at page 7.

Bye,
Jacopo
#6
06/22/2006 (1:05 pm)
Jacopo,

Yes, this is the desired approach. However, this does not work in the 1.1 release either. Nothing that I have found thus far is an elegant solution for direct mouse-controlled games. If you just need mouse window events, like clicks, drags, and mouse cursor position, then it's fine. Anything that you need to "bind" to the mouse for is broken.
#7
06/22/2006 (1:18 pm)
IT WORKS! Peter, many thanks for your tip. Indeed, a simple call to lockMouse(true); is all that was needed! It's SO great to learn what I need is already built into the engine. Yet again, TGB proves to be better than expected.
#8
06/22/2006 (4:19 pm)
@J. Alan

Yes, you are right.
I've tested the .bind commands and it seems that only the Buttons works, but not the movements.
I'm looking at the C++ source to see if I can find the problem. I will post here if I found something.
I remember that this approach worked with the old 1.0.2 release, so I will probably start by making comparisons beetween the releases.

Bye,
Jacopo
#9
06/22/2006 (4:45 pm)
If you hide the cursor it works

hideCursor();

though be sure to do that in script, if you toggle the console off or on it unhides the cursor so just in the console won't work (unless you schedule it in the console)
#10
06/22/2006 (4:58 pm)
Aha! The answer I have been looking for for a few weeks! I don't know why I didn't think of it before, since this is how TGE works also. Thank you for informing us.

Note that there is no event passed through when you stop moving the mouse (because really, there is no event there...). All this means is that if you tie the mouse to set a velocity or acceleration, you will see some "drifting." This is definitely not a bug, it's just a feature of how the mouse works. This can be accounted for in several ways that will be left as an exercise for the reader.
#11
06/23/2006 (2:19 am)
Hi Matthew.

Thanks for the info!
I tryed with the old CursorOff() (Doesn't exists anymore) and Canvas.cursorOff() with no success, but with your suggestion works perfectly.

Bye,
Jacopo