General Purpose Smooth Scrolling Module

Started by subspark, Sat 01/12/2007 04:12:08

Previous topic - Next topic

subspark

Dear AGS'ers, AGS is in need of a decent general purpose smooth scrolling module. At first I though scrolling the viewport would be easy, it wasn't. I had to visit several topics in order to get a specific and messy solution. The reality is, we all need a module that can be activated in room scripts for introductions and cut scenes.

A simple line like:
Code: ags
ScrollViewport(left,easing, blocking, 91 pixels);

or:
Code: ags
ScrollCharacter(up,easing, blocking, 12 pixels);

even:
Code: ags
ScrollGUI(up right,easing, blocking, 20 pixels);


...should give an indication as to the simplicity and control needed.

Can somebody please put the whole scrolling thing to rest with a decent module for the new AGS 3.0?
With all due respect to Chris, simple 'one line' control over scrolling could have been a standard AGS feature a long while ago, but it's now come time for the community to do it ourselves.  :)

Anyone keen?

Cheers,
Paul.

monkey0506

What exactly would be the purposes of the ScrollCharacter and ScrollGUI functions? Should ScrollCharacter make the Character walk up the 12 pixels, or should the Character float up?

A Viewport scrolling module wouldn't be that difficult, and it could even be set up with enumerated values like:

Code: ags
eScrollUp,
eScrollDown,
eScrollLeft,
eScrollRight,
eScrollUpLeft,
eScrollUpRight,
eScrollDownLeft,
eScrollDownRight


Also I don't understand the purpose of the easing parameter. Presumably a function like this you would pass SPEED and AMOUNT parameters and every game loop it would scroll SPEED units in the specified direction up to AMOUNT pixels. This is what you're looking for, yes?

subspark

Characters and Objects would float yes.

Easing is a pretty common term. It basically means instead of a linear movement from point A to point B, its more like a 90 degree S curve. (If you've ever done 3D animation?)
The scrolling motion slows down (eases into) the stop position and eases out from a stationary position into movement. This could mean a mere 3 extra frames to slow down sharp stopping and starting.

Its all been done before http://www.adventuregamestudio.co.uk/yabb/index.php?topic=25937.0 and http://www.adventuregamestudio.co.uk/yabb/index.php?topic=31187.msg401225#msg401225
however none of these solutions are as simple as typing a single line of code into a room script. Theres no reason it shouldn't or can't be that simple and every reason it should be.

I'm not a programmer but I do hope that somebody can create this module for the community. In my opinion, its something that once created, we won't do without it.

Cheers,
Paul.

Khris

Here's easing using sine & cosine:

Code: ags
enum EaseType {
  eEaseIn,
  eEaseOut,
  eNoEasing
};

int Scroll(int min, int middle, int max, EaseType easing) {
  if (min==max) return 0;
  float part=IntToFloat(middle-min)/IntToFloat(max-min);
  if (part<0.0) part=0.0;
  if (part>1.0) part=1.0;
  if (easing==eEaseIn) part=Maths.Sin(part*Maths.Pi/2.0);
  if (easing==eEaseOut) part=1.0-Maths.Cos(part*Maths.Pi/2.0);
  return FloatToInt(part*IntToFloat(max-min))+min;
}


Use it like:
Code: ags
  int c;
  while (c<100) {
    gStatusline.SetPosition(0, Scroll(0, c, 100, eEaseOut));
    Wait(1);
    c++;
  }

tested, working

Note that in the case of easing out, the end speed is not 1 but Pi/2 (~1.57).

subspark

As I said I'm no programmer. Could you give me a little reference as to where to put this code?
I imagine the main bit goes in repetedly_execute_always?
In any case, I couldn't get it to work.

Do I have any control over the scroll direction with this script, btw?
I don't understand what does what so your going to have to be more specific with me.  :)

Thanks,
Paul.

Khris

#5
The first bit goes at the top of the global script. You can move the enum's definition into the script header if you want.
If you want to access Scroll() from your room scripts, you need to import the function in the header:
Code: ags
// inside script header, below enum definition
import int Scroll(int min, int middle, int max, EaseType easing);


The second bit demonstrates the use; the while loop makes c go from 0 to 99, Scroll() is called with 0 and 99 as from-value and to-value and c as the intermediate position.
The fourth param sets the type of easing; if it's set to eNoEasing, Scroll() will simply return the unchanged intermediate value.
If easing is set to eEaseIn or eEaseOut, the intermediate value is adjusted to reflect acceleration or deceleration.
That's all Scroll() does; the actual movement has to be done using a loop.
So the direction of the scrolling can be anything you like.
If you get my code to work, put "99-" in front of "Scroll(..." to reverse the direction.

(I'm working on an adjusted function that'll use a factor to scale the speed.)

subspark

#6
Hmm, still doesn't work. I'm actually trying to scroll the viewport here so the line gStatusline doesn't apply.
The code I have is SetViewport(0, Scroll(0, c, 100, eEaseOut)); but this doesn't work either.
Any ideas?

QuoteI'm working on an adjusted function that'll use a factor to scale the speed.
Brilliant! I'm looking forward to trying it out.

Thanks for this, Khris. Your a champ!  ;)

Edit: Whats eEaseOut and how would you use it? This doesn't make sense.
The whole purpose of easing is that it does the ease out on the start of scrolling and the ease in at the end to smooth out the sharp transition.
Based on your example, I don't understand how one would write the eEase in command for the end of the animation.
Does this mean I have to write two separate lines of code to control each half of the scrolling movement? Point A = eEaseOut line and point B = eEaseIn line?  ???

Cheers,
Paul.

theatrx

Quote from: KhrisMUC on Sat 01/12/2007 17:41:08
Here's easing using sine & cosine:

Code: ags
enum EaseType {
  eEaseIn,
  eEaseOut,
  eNoEasing
};

int Scroll(int min, int middle, int max, EaseType easing) {
  if (min==max) return 0;
  float part=IntToFloat(middle-min)/IntToFloat(max-min);
  if (part<0.0) part=0.0;
  if (part>1.0) part=1.0;
  if (easing==eEaseIn) part=Maths.Sin(part*Maths.Pi/2.0);
  if (easing==eEaseOut) part=1.0-Maths.Cos(part*Maths.Pi/2.0);
  return FloatToInt(part*IntToFloat(max-min))+min;
}


Use it like:
Code: ags
  int c;
  while (c<100) {
    gStatusline.SetPosition(0, Scroll(0, c, 100, eEaseOut));
    Wait(1);
    c++;
  }

tested, working

Note that in the case of easing out, the end speed is not 1 but Pi/2 (~1.57).

Sometimes I look at your coding and think... Damn!  What was he on to come up with that elegantly simple solution.  Good work.  Steve
Life is a banquet and most poor sonsofbitches are starving to death

subspark

Care to share with us how you got it working, Steve?

I'm still not sure what your eEaseOut does, Khris.
Cheers,
Paul.

Khris

Here's a graphical representation of what the function does:



The x-axis represents time, the y-axis represents location. Thus, the slope of the graph represents the speed.

Standard scrolling is done using the blue line. Even movement at a constant speed.
The red line represents easing in; the movement starts out fast and slows gradually down to zero.
The green line represents easing out; the movement starts very slow and gradually gains speed.

The starting/ending speed is ~1.57; unfortunately, this can't be changed by simply including a factor in the function params.
Pi/2 isn't very convenient to handle so I've changed the function to use x² instead, resulting in a starting/ending speed of 2:

Code: ags
int Scroll(int min, int middle, int max, EaseType easing) {
  if (min==max) return 0;
  float part=IntToFloat(middle-min)/(IntToFloat(max-min));
  if (part<0.0) part=0.0;
  if (part>1.0) part=1.0;
  if (easing==eEaseIn) part=-1.0*(part-1.0)*(part-1.0)+1.0;
  if (easing==eEaseOut) part=part*part;
  return FloatToInt((part*IntToFloat(max-min)))+min;
}


To make the status line accelerate from 0-100, then move steadily to 200, use code like this:
Code: ags
    int c, y;
    while (c<299) {
      if (c<200) {
        y=Scroll(0, c, 200, eEaseOut)/2;
      }
      else y=c-100;
      Wait(1);
      gStatusline.SetPosition(0, y);
      c++;
    }

subspark

Interesting. Ultimately I would like to know how to work through your code to achieve this:


I'm aiming for easing in general. So smooth out from point A to smooth in to point B.

Paul.

Khris

To achieve this you don't even have to use any conversions, you can directly use both versions one after another:
Code: ags
    int c, y;
    while (c<199) {
      if (c<100) y=Scroll(0, c, 99, eEaseOut);
      else       y=Scroll(100, c, 99, eEaseIn);
      Wait(1);
      gStatusline.SetPosition(0, y);
      c++;
    }

subspark

I'm with you now. However when using it with SetViewport the screen jumps to the value rather than performing any scrolling at all.
I've adjusted the code like this:


Code: ags
function room_AfterFadeIn()
{
  Wait(80);
    int c, y;
    while (c<199) {
      if (c<100) y=Scroll(0, c, 99, eEaseOut);
      else       y=Scroll(100, c, 99, eEaseIn);
      Wait(1);
      SetViewport(0, y);
      c++;
    }
  ReleaseViewport();

  FadeObjectIn_NoBlock(oLocationTime,0, -15); // Paracosmo City 2083
  cNarrator.ChangeRoom(3, 120, 0);
}


Am I missing something somewhere?

Cheers,
Paul.

Khris

Oh, I forgot to change something. The 99 in the eEaseIn line was supposed to be a 199.

Use this:
Code: ags
function room_AfterFadeIn()
{
  Wait(80);
  int c, y;
  while (c<200) {
    if (c<100) y=Scroll(0, c, 100, eEaseOut);
    else       y=Scroll(100, c, 200, eEaseIn);
    SetViewport(0, y);
    Wait(1);
    c++;
  }
  ReleaseViewport();

  FadeObjectIn_NoBlock(oLocationTime,0, -15); // Paracosmo City 2083
  cNarrator.ChangeRoom(3, 120, 0);
}

subspark

Hmm. It compiles and launches fine however it still jumps without scrolling. I wonder if its something in my global script. I've tried searching through my scripts looking for bugs but nothing is immediately obvious. Boy, either I'm really stupid, or I'm missing the obvious.  :-[

Cheers,
Paul.

Khris

When I tried your code, the first, accelerating scroll worked fine. The second one didn't due to the 99.
The corrected code is tested and worked for me; the 320x400 screen scrolled nicely from top to bottom.

subspark

#16
Hmmm. Let me see what I am doing wrong. I must have some conflicting code in my game somewhere. I did use two different methods before I tried yours so there might still be some left over code.
I can't believe how simple your function is, btw. I'm astounded that smooth scrolling can be done with merely a few lines of code. Thats pretty impressive to me, man.

Paul.

EDIT: Ahah! Top to bottom you say. Thats where my error is. I was wondering why it never worked at all. I'm actually trying to go from right to left. It makes sense now. This must be pretty simple to do so I'll have a play around.

Thanks Khris.

subspark

Ive tried everything! I can't seem to get it to scroll in the direction I want. I want it to scroll 91 pixels to the left. This is what I have so far:
Code: ags
function room_AfterFadeIn()
{
  Wait(80);
    int c, x;
    while (c>0) {
      if (c>45) x=Scroll(91, c, 46, eEaseOut);
      else       x=Scroll(45, c, 0, eEaseIn);
      Wait(1);
      SetViewport(x, 0);
      c++;
    }
  Wait(100);
  ReleaseViewport();

  FadeObjectIn_NoBlock(oLocationTime,0, -15); // Paracosmo City 2083
  cNarrator.ChangeRoom(3, 120, 0);
}


I know I'm somewhat there.

Cheers,
Paul.

monkey0506

Code: ags
int Scroll(int min, int middle, int max, EaseType easing) {
  if (min==max) return 0;
  float part=IntToFloat(middle-min)/(IntToFloat(max-min));
  if (part<0.0) part=0.0;
  if (part>1.0) part=1.0;
  if (easing==eEaseIn) part=-1.0*(part-1.0)*(part-1.0)+1.0;
  if (easing==eEaseOut) part=part*part;
  return FloatToInt((part*IntToFloat(max-min)))+min;
}

function room_AfterFadeIn()
{
  Wait(80);
    int c, x;
    while (c>0) {
      if (c>45) x=Scroll(91, c, 46, eEaseOut);
      else       x=Scroll(45, c, 0, eEaseIn);
      Wait(1);
      SetViewport(x, 0);
      c++;
    }
  Wait(100);
  ReleaseViewport();

  FadeObjectIn_NoBlock(oLocationTime,0, -15); // Paracosmo City 2083
  cNarrator.ChangeRoom(3, 120, 0);
}


I'm not sure, but I think you may have meant c < 45 instead of c > 45. Without actually seeing it in motion, the numbers I calculated aren't really meaning much to me right now.

Please forgive me if I'm completely wrong here. My toe and head are experiencing reasonable amounts of discomfort right now and my brain could just be floating off elsewhere... ::)

subspark

Thanks monkey. I already tried that actually. That time it didn't even pop to the left side.  :)

Paul.

SMF spam blocked by CleanTalk