Screen Scroll and Tuggy Mouse?

BlitzMax Forums/BlitzMax Beginners Area/Screen Scroll and Tuggy Mouse?

Robby(Posted 2012) [#1]
Hi guys! New to blitz, not so new to programming but like this a lot! Basically I'm re-creating and enhancing an old RPG game I made, using a diamond tile system and all is fine except a timing issue.

In my main loop I have a function call to update my mouse. Its mapped to point the right way depending on where it is on the screen. This updates perfectly and almost instantly.

I also have the screen scroll with a little character in the middle.
Obviously, the screen scroll is uber-fast and needs slowing. I can do this with a Delay(75), right before my Flip. Fine, but now, of course, the mouse update starts getting "tuggy".

So I tried removing delay and calling the scroll routine about every 9th iteration of the loop.
Now the mouse updates swiftly again, and the guy walks at the right pace also. Problem solved! Almost.....!

Trouble is, because I use: scroll when gtime/9 = Int(gtime/9) as my way to call the screen scroll every 9th iteration while checking for MouseDown, it means my mouse misses often if a player is going like click click click, instead of HOLD, as they might in navigating tight passes, etc.

So basically I'm trying to keep my loop with the mouse pointer update fast, maybe Delay(1), but the scroll at about Delay(75) without my mouse getting tuggy in one case, or less responsive in the other.

Any ideas how I can synchronize this better? A million thanks, and happy to be part of the Blitz community!

-Dr. Dungeon
-In The Castle of
-The Mach Gryphon


xlsior(Posted 2012) [#2]
The game logic itself and the drawing of the screen do not have to be in sync. Your logic (e.g. look at the clicks, queue a command, etc.) can still happen, even if the screen does not yet get redrawn.

That way each of the click-click-click's gets seen by your program, you are just waiting for a certain time interval to pass before you draw your changes to the screen.

Now... I don't know what 'gtime' signifies, but keep in mind that not every computer has the same speed, so you should keep the timing neutral so the game still runs at the proper speed on a computer a thousand times as fast as yours.


DrDeath(Posted 2012) [#3]
It's probably better when you take your game time not by loop cycles, but by actually passed milliseconds. That would also fix the potential problem with different CPU speeds xlsior mentioned.


Robby(Posted 2012) [#4]
Thanks for the feedback guys. I'm ok with the over all loop speed. I have a Delay(1) at the bottom right after flip, so it will never go faster than that on any computer.

The trouble is this line:
If MouseDown(1) And MouseX() < 710 And MouseY() < 710 Then
If mdm = 0 Then
DelTime = 150
mousewalk()
End If
End If

That mdm thing is just a flag to it doesn't re-enter the function before its done (recursion). The 710's are the area within my screen view.

mousewalk() gets the direction to scroll.

Inside the loop is also this: (mda is another avoid recursion flag)
If MouseX() < 710 And MouseY() < 710 And mda = 0 Then
HideMouse
arrowget()
DrawImage arrows[marrow],MouseX()-1,MouseY()-2
Else
ShowMouse
End If

This says, if you are in my screen view, go get the correct mouse pointer graphic, east, west, etc. The -1, -2 is just a tiny fine-tune offset.

At the end, right after the Flip:
Delay(DelTIme)

So this all updates real fast. DelTime is set to 1 with:

If Not MouseDown(1) Then 'sneaky mouseUP!!!
DelTime = 1
End If


Now you can see what's happening. If I had no delay loop, my character goes like a demon. I need to slow him down - the scroll - thus the DelTime = 150 above.

That's fine, but the line that draws my arrow is now also slowed up. Its not bad, but a little because whenever the mouse is held down, its delaying 150 as it needs to in order to scroll the screen at a nice, slower speed.

When you release - stop walking - DelTime is 1, mouse updates super-fast.
But only until you click and hold to walk, then its 150 again, scroll fine, mouse tuggy.



I tried changing it by hardwiring delay to Delay(1) - let it go as fast as it wants - but then call the walking routine maybe every 9 or so loop iterations.
(This in effect slows the scroll time while letting the mouse update fast.)

gtime = gtime + 1 'its a float

If gtime/9 = Int(gtime/9), "If gtime divided by 9 equals the integer value of gtime divided by 9, then do the scroll.

This, in fact, will happen every ninth number, or one in nine times - or whatever number we choose.

The result is no Delay beyond 1 is there to slow up the mouse pointer graphic, and my screen then scrolls at a decent, slower pace, because its happening only one in nine loop iterations. A brilliant solution! Or so I thought...

The trouble is, he needs to go to scroll when its a ninth iteration AND the mouse is down:
If MouseDown(1) AND gtime/9 - Int(gtime/9) ' do the scrolling

So obviously, he's gonna miss some mouse downs!!!!!

So either I get a swift non-tuggy mouse that isn't always responsive on mousedown, OR I get a super good mousedown response, but tuggy when rolling it around the screen while walking my character!

What I'm trying to do, then, is keep the mouse updating fast, like Delay(1),
but the screen scroll at about Delay(150) - I can't seem to do both at once.

As long as I have at least that Delay(1) I can control the loop speed on any machine. Its making the mouse go fast and the screen slow at the same time that's the problem. I see games all the time where the mouse is as fast as it ever is, irregardless of how fast stuff is happening in the screen view.


I run into this a lot, when we solve one problem and a new one crops up. When that happens, I usually learn that something fundamental must be changed. In this case, maybe run the mouse pointer update in another thread somehow? Like when we go to a web page and its loading really slow, notice the mouse can still be whipped around the screen. Its like a separate process. I need to do something similar, I think.
Thanks again! -Dr. D.


Midimaster(Posted 2012) [#5]
Using DELAY is not very useful, because you never know, how long the code needed, until it reaches the DELAY command line.

You should change to a Millisecs() based main loop or use a WaitTimer() call to get fixed speed on every computer:

This makes your came cycle 60x a second and independent from real computer speed:
Global FPS:TTimer = CreateTimer(60)
MyMouseDown%  ' flag for the mouse button
Repeat
     Cls
     ' do all your game stuff here:
     MyMouseDown=MouseDown(1)
     If MyMouseDown=1 And MouseX() < 710 And MouseY() < 710 Then
          mousewalk()
     Endif
     ' do all your display stuff here:
     
     Flip 0
     WaitTimer(FPS)
Until Keyhit(KEY_ESCAPE)

every cycle will need 16msec now, and the rest of performance will be given back to system. Now you have to adjust the values of pixels added to the player, or scrolling , etc...



In the second model, you can use the maximum speed of each computer, the framerate will rise upto 200FPS, but the Millisecs() will keep the scrolling perfect:

Global MyScrollTime% ' variable for a timestamp
Global MyMouseDown% 
Repeat
     Cls
     ' do all your game stuff here:
     MyMouseDown=MouseDown(1)
     If MyMouseDown=1 And MouseX() < 710 And MouseY() < 710 Then
          If MyScrollTime<Millisecs()
               MyScrollTime=Millisecs()+150
               mousewalk()
          Endif
     Endif
     ' do all your display stuff here:

     Flip 0
     Delay 1
Until Keyhit(KEY_ESCAPE)

In this model you have to adjust the values behind the Millisecs() command line to adjust the speed of objects. You can use as many TimeStamps as you want, they are only integer variables.


Most of us use a combination of both: The fixed framerate of 60FpS for painting the screen, but individual timing on game movements:

Global FPS:TTimer = CreateTimer(60)
Global MyScrollTime% , MyMouseDown%
Repeat
     Cls
     ' do all your game stuff here:
     MyMouseDown=MouseDown(1)
     If MyMouseDown=1 And MouseX() < 710 And MouseY() < 710 Then
          If MyScrollTime<Millisecs()
               MyScrollTime=Millisecs()+150
               mousewalk()
          Endif
     Endif
     ' do all your display stuff here:

     Flip 0
     WaitTimer(FPS)
Until Keyhit(KEY_ESCAPE)



Robby(Posted 2012) [#6]
Thanks for the advice, although I'm a little confused at how WaitTimer differs from delay.

My code is very similar to your second example with one difference - right before the flip, this:


Repeat

' game stuff

' If MouseDown, do mousewalk, etc.

' update arrow pointer last - needs to be on top of everything.
If MouseX() < 710 And MouseY() < 710 And mda = 0 Then
HideMouse
arrowget()
DrawImage arrows[marrow],MouseX()-1,MouseY()-2
Else
ShowMouse
End If

Flip

Wait or Delay: 1 if mouse up, 150 if down and walking

Until Keyhit(KEY_ESCAPE)


Wether its a wait or delay or milliseconds, my problem isn't different speeds on different computers. Your code would indeed work fine for that.

The problem is arrowget()
What that does is quickly look up the X and Y of the mouse, and use those values to look into an array - a map of my screen - for the proper mouse pointer.

These are eight .png pictures I drew of a white arrow pointing east, west, etc. in the eight cardinal directions.

arrowget() simply looks up which arrow png to draw on the screen depending on where the MouseX() and Y() are.


So if the user moves the mouse to the lower right of the screen, it turns into a southeast arrow, if he rolls to the right, its an east pointer. These are stored in a little TImage array of 46x46 graphics.

So in effect, the pointer is being drawn just like my map and little guy.

Now you can see the trouble. I need arrowget() in the loop b/c the pointer must always be updated. This is quite fast - almost as fast as the system pointer.

Similarly, the scrolling goes wonderfully fast and I'm happy with that because I know I have some "room", so to speak, for more complex logic later.
(Blitz is much faster than REALBasic's canvas system :) )

I even have little animations in for the guy and it looks great! He walks along, swinging his arms, in sync with the scroll.

Trouble is he's running like 200 miles per hour!

Enter the delay or wait of some sort. 150 seems about right. He's walking about 2 steps or so a second and its a realistic motion.

Now look through the code and slow everything down many times:

- Loop is buzzing along, nothing special happening except arrowget() is begin called over and over as it must.
- User moves mouse to east. arrowget() correctly draws east-facing .png at MouseX() and MouseY()
- User moves mouse fast - still no problem. Dr. Dungeon's wonder code works great! :) The mouse updates almost instantly as designed.

-Player presses mouse while east arrow displayed.
- pause interval is now set to 150 so he walks normal pace.
- code returns from mousewalk()
- all is displayed, user still holding mouse, guy walking east now
- a pause of 150 occurs.

Safari s'goodie!!

- user continues holding mouse. Guy walks, mouse still points east.

- player sees monster east! Sees a clear field SOUTH.
- player moves mouse quickly to bottom of screen.

- Flip happens, delay of about a half second occurs.

- player has mouse moved to bottom of screen, but pointer doesn't get there until about a half second later!!!

THAT'S the problem.

Let's suppose I had a feature where the character could sneak real slow, like maybe 1 step every two seconds, maybe Delay(2000) or something similar.

Imagine how lousy the mouse would look!

In the old days - remember we used to do delays like this:

For j = 1 to 999
Next J

In i86 or even TRS-80 days anyway :) But what I would do then is something like:

For j = 1 to 999
arrowget()
Next j

So my arrow would update even WHILE the delay is occurring. I need to do something analogous to this.

What I did back in REALBasic was use a timer. These ran completely separately from other code. So when you did a mouse down, it would simply turn it on and all the walking/scrolling was inside the timer. MouseUP turned the timer off. (along with some code to stop it if you just clicked quick)

You can see then that my real problem is drawing the arrow on the mouse. If I don't do that, and just use the system mouse, its fast.

There's some way to do this. The game "Eschalon" is very smilar to what I'm making - his character walks at a slow, realistic pace, while his custom mouse pointer updates as fast as you roll it around the screen wether walking or not. That game was done in Blitz - not sure how he did his mouse.

Oh well! Thanks for your time and comments. I could live with the system pointer, but it doesn't look as NEATO as my arrows. Gotta be some way to do this?? - Rob


Jesse(Posted 2012) [#7]
1 Don't use Delay.
2 Use a millisecond counter to update animation frames.
3 use floats and delta timer to control the character speed ( look up delta timing; a lot of threads here about that).

those three things should clear all of the problems you are having.

if you can't figure it out from there, you are just going to have to post your code so we can help you.
or you can email it to me and I will help you solve it.


Robby(Posted 2012) [#8]
I'm not sure how the millisecond counter will work. My animation is is just the drawing of the character in the middle right after all the tiles draw, simulating motion. Here's the actual code in the main loop. I'll try the code posting thing - new to this, not sure it will work.




Robby(Posted 2012) [#9]
Look at the code right after:
xyou = MX + 16
yyou = MY + 15

Change to:


It is simplicity itself! :)
-Dr. Dungeon


Jesse(Posted 2012) [#10]
that code is too abstract for anybody to understand so I am kind of guessing.
from what I understood, I guess you are not really animating. you are just drawing the character facing in the direction desired? yea, that does not account for animation.

also the most important thing thing to understand is that you don't change the processing speed to compensate for the speed of the characters. You do it the other way around. if moving one pixel at a time is too fast then you move 0.5 pixel at a time or less or whatever adjusts the movement of the character to the correct speed. same thing for anything else that moves too fast.

the flip is supposed(don't work on all computers) to set the frame rate to a fixed speed so you can have a smooth animation. but because the computer have programs running in the background that is not always going to be consistent. but that is a little bit advanced for you right now. you need to get past the floating point movement.

also if you really want help you are going to have to post the whole program in a zip/compressed file so we can point out exactly what you are doing wrong. its way to hard to guess from that code to many acronyms only you understand.


Midimaster(Posted 2012) [#11]
I keep advising you, not to use "timer functions" like this:
gtime = gtime + 1
     If gtime/7 = Int(gtime/7) Then
         ....

they do not result in a constant timing, not on one computer, never on others...

The delay function has a use, but not in the causes you need it. A possible solution would be to prevent the computer from hanging up, if code is to stressful for the processor or graphic card, or if you want to ensure, that the system can react on your code:
Repeat
     ......
     ; all your code
     .....
     Flip 0
     ; anti stress delay:
     Delay 1
     WaitTimer FPS



......
     ; call a third party process
     SendGNetMessage(....
     ; ensure, that a other process could do his work:
     Delay 150
     GnetListen(.....


There is no use for a a DELAY with a lenght of 150 in a regular program. This is often used in background applications which only wait the whole day for doing something. They often "sleep". And this is realized with a DELAY 150.

To receive Users MouseEvents 100% it it necessary to check the system 60 times a second. With a DELAY 150 you may miss events or just cannot see them.


And I keep on saying that the players movement timing has to be independent from the main loop timing!!!

here is a demonstration:



SystemError51(Posted 2012) [#12]
Just to answer the delay-question -

The Delay-function blocks further execution of your program until the specified amount of milliseconds has passed.

Other approaches do not.

You could also look into threads.


LunaticEdit(Posted 2012) [#13]
I just grabbed BB last weekend so I'm still very noobish, but what I've done is subclassed my game 'scenes' with the following prototype class:

Const SCREEN_MainMenu         : Int = 1
Const SCREEN_CharacterCreator : Int = 2
Const SCREEN_MainGame         : Int = 3

Type TGameScreen
	Field Terminate : Int = False
	Field NextScreen : Int = 0
	Method Initialize() Abstract
	Method Finalize() Abstract
	Method Render() Abstract
	Method Update() Abstract 
EndType


Then in the high level logic I can call render and update all I want. So basically instead of doing render, then input/logic stuff.. simply put your render logic in one function, and update/input logic in another... Then you can use simple timing tests and 'offset' your map based off of that. Here's how I do FPS independent movement while also not using a delay():

		oldTime = time
		time = MilliSecs()
		frameTime = Double(time - oldTime) / 1000.0
		moveSpeed = frameTime * 2.0


Of course for best results you'd want your offset to be floating point, but you can always Int() them in the main rendering logic. But at the very least, if you absolutely must delay, you can simply keep calling render until the number of msecs required is passed, call update(), reset the timer, and go again. I'd still recommend using frame independent motion though :)

Last edited 2012