Adventure Game Studio

AGS Support => Beginners' Technical Questions => Topic started by: miguel on Tue 15/01/2013 10:10:29

Title: optimizing loop
Post by: miguel on Tue 15/01/2013 10:10:29
So, I've built a very simple but working little dungeon game.
Enemies detect the hero presence and engage according to distance, fights occur with different stats for every character, etc...
Making it working was a big achievement for me, but now I would like to optimize it.
My question is: how can I stop the engine from removing player control when blocking events happen; I know I am answering my question, I can't if they are blocking. But I've seen it done.

I've tried CC for npc animations with no success and breaking function code into smaller portions also with no success.
Do I require a tile-engine?
What's the concept behind allowing the engine to perform such "arcade" behaviours?

Everything in my game runs in Rep_Execute.
Title: Re: optimizing loop
Post by: Crimson Wizard on Tue 15/01/2013 10:18:54
I am not sure I understand. Do you mean, there are actions that are explicitly blocking, or do you mean that some process runs so long, that game appears hung for a moment?

Also, what is "CC"?
Title: Re: optimizing loop
Post by: miguel on Tue 15/01/2013 11:43:16
CC is , (sorry for that), CharacterControl module.
I mean that if a animation takes place it will block the game until it finishes and setting it to non-blocking will (because all my variables allow it) process things very fast.
I know it is the proper behaviour of the engine but I've seen examples where functions run without the player loosing control.
Title: Re: optimizing loop
Post by: Crimson Wizard on Tue 15/01/2013 12:01:33
In this case the topic title is misleading. It is not about optimizing your function (making it faster), but about synchronizing character animations with game process? Well, or something like that, I guess.

If your game is arcade you obviously cannot use blocking animations, at all (except for cutscenes maybe). You must redo your game logic so that animations run in non-blocking fashion. I do not know what you did there and why things are processed too fast, so I can't tell whats wrong exactly.

In general case, every entity (e.g. enemy) remembers its current state, and acts according to its state. It changes state when certain event occurs, or certain time period passes.
Let's take an example. There's one enemy that can walk towards player and attack.

- If enemy is too far, you set him to Chasing state. If nothing else happens, it checks for player position, like, every second, and fixes his destination accordingly. Every time (once a second), he starts Chasing (walking) non-blocking animation.
- If enemy is in Chasing state and comes close to player, you set him to Attack state and start Attack non-blocking animation (like, he swings his sword).
- If enemy is in Attack state, and his (non-blocking) animation finishes, you check if player is still in range, and if he is, then player looses some health. Then enemy is set either to Attack state again (if player is close) or to Chasing state.
In either case all this requires checking enemy state once in a while (higher frequency gives smoother action, but also increases processor load).

So far we examined timed processing. Let's take an example of events. What if player attacks enemy while he is in Chasing state? He then would interrupt chasing state and bring enemy to some "Damaged" or "Hit" state, which will make him to play "Pain" animation (non-blocking!). While this animation is played, enemy cannot switch to Chasing not Attack. So you make this check repeatedly too (see if the animation is finished).
If player continues to deal damage and kills enemy, enemy is set to death state and plays Death animation, after which he is no longer processed and left as a dead body, or just dissapears.


BTW, some time ago I made this:
http://www.mediafire.com/?exxi3y0ddy1
It does not use any blocking functions at all. Probably I could find a source code... I don't remember is it good enough to show though :).
Title: Re: optimizing loop
Post by: miguel on Tue 15/01/2013 12:24:07
You are right about the topic title being misleading.
I understand what you're saying. Should I use global variables to set bool states and Rep_Execute to keep checking it?
A "iniciative" function could do the check, like after the non-blocking animation finishes it checks again for who has the iniciative?

Downloaded your demo, would be great to have the code.

Title: Re: optimizing loop
Post by: Crimson Wizard on Tue 15/01/2013 12:33:52
Quote from: miguel on Tue 15/01/2013 12:24:07
Should I use global variables to set bool states and Rep_Execute to keep checking it?
In one of your previous topics I saw you have a struct for enemy? You can just add "state" variable there. I don't think boolean variables for states is a good thing, because you will need a variable for each possible state. It is probably better to have integer, or enum type.
For example:
Code (ags) Select

enum EnemyState
{
  eStateRunning,
  eStateAttacking,
  ...etc
}

Then you declare variable as
Code (ags) Select

EnemyState state;


And, yes, you check it in repeatedly execute function. Also check Character.Animating property - it tells if character is currently playing any animation.

I will try to find the code, but I think there was just a loop which cycled all enemies, containing one big switch which selected what do do depending on their state, and whether the animation is finished or not.
Title: Re: optimizing loop
Post by: miguel on Tue 15/01/2013 12:37:05
Got it, I'll dive into it right now.
Title: Re: optimizing loop
Post by: miguel on Wed 16/01/2013 14:14:21
Okay, I defined states for every little bit of what's happening in the game and yes, I've got it running towards what I had in mind.
My issue here is where do I unlockview of the characters after animating? Unlocking it is making the game freeze.

Edit: I'm using on_mouse_click to unlock views now, it's working. Is this the best approach? The only one?
Title: Re: optimizing loop
Post by: Crimson Wizard on Wed 16/01/2013 17:46:34
I found my project... and speaking frankly I am a bit depressed seeing how overcomplicated it is. I was using monkey_05_06's Stack to store enemy data, and now I am not sure it was good solution. Lots of code is only about reading and writing data to string. Today I would probably just use static arrays.
Also, it is WIP, and I made some mistake there that causes an observable bug. I can't fix it right now (may take a time for me to refresh algorythms in memory). :-\ So, not sure if that will be of any use, honestly. But here it is:
http://www.mediafire.com/file/a3nx46dnsfmvcv3/ShootingGallery.7z


Quote from: miguel on Wed 16/01/2013 14:14:21
Okay, I defined states for every little bit of what's happening in the game and yes, I've got it running towards what I had in mind.
My issue here is where do I unlockview of the characters after animating? Unlocking it is making the game freeze.
And why do you need to lock views? Unless character moves, it won't animate by itself anyway.

Quote from: miguel on Wed 16/01/2013 14:14:21
Edit: I'm using on_mouse_click to unlock views now, it's working. Is this the best approach? The only one?
I am confused. I totally do not understand why do you have to unlock views upon mouse click? What are you trying to do?
Title: Re: optimizing loop
Post by: miguel on Wed 16/01/2013 20:01:26
Yes, locking the view was a bad mistake due to being doing it for years!
But mouse click is fine to change the view back to the walking cycles. Using changeview after the non-blocking animation would cut it short, no?

Thanks for the files, I'm sure it's a great way to study ags coding.
Title: Re: optimizing loop
Post by: Crimson Wizard on Wed 16/01/2013 21:22:44
Quote from: miguel on Wed 16/01/2013 20:01:26
But mouse click is fine to change the view back to the walking cycles. Using changeview after the non-blocking animation would cut it short, no?
Idea is to check current state in rep_exec function over time.
If you need to call ChangeView right after some animation is finished, you do something like:
Code (ags) Select

// Has non-blocking animation finished?
if (!Char.Animating)
{
   Char.ChangeView...
}

Of course you may need more conditions there, like check what is a current View, or some variables.
By the way, I now recalled that in my game I did not set any "state" variable, but used current character loop as a definition for what is character doing, like attacking, dying etc. Like, loop 10 is melee attack, loop 11 is ranged attack, and so on.
Title: Re: optimizing loop
Post by: miguel on Wed 16/01/2013 23:07:03
Crimson, I've just finished examining your works and I find it inspiring...in a Dwarf Fortress sense.
It's really too much information at one go and I'll be going there from now on for sure. It's great to actually look at the stuff I read from you, monkey, khris and the rest. I admire you guys. And thank you for your patience.

P.S: my little dungeon runs fine, not as smooth as your demo (impressive) and I've been adding enemies and chests, keys and doors...It feels nice.
Title: Re: optimizing loop
Post by: Crimson Wizard on Thu 17/01/2013 07:37:04
Quote from: miguel on Wed 16/01/2013 23:07:03
It's really too much information at one go and I'll be going there from now on for sure.
As I said, there's too much extra code, not related to enemy AI. I made a mistake trying to put everything into one module... this should be split into data storage / AI parts. AI functions themselves are not so big or difficult. Also lack of comments... argh! :)
Maybe I'll find some time to improve this project.
Title: Re: optimizing loop
Post by: miguel on Thu 17/01/2013 12:24:22
Yup, comments would be a gift from the gods!
Enemy AI is the next step on my list.

Anyway, is it safe to say that (in Rep_Execute) the more I split the code into small chunks the better it runs?

Title: Re: optimizing loop
Post by: Crimson Wizard on Thu 17/01/2013 12:33:56
Quote from: miguel on Thu 17/01/2013 12:24:22
Anyway, is it safe to say that (in Rep_Execute) the more I split the code into small chunks the better it runs?
Is this something that you see in practice?
I would not say anything like that for any programming language, but I remember there was an interesting effect, which I observed in Gepard's "WOO" (http://www.adventuregamestudio.co.uk/site/games/game/1224/), but could not explain back then. As more characters arrived at the scene, the game started to slow down significantly.
I was helping Gepard to improve the code, and originally there was a HUGE repeatedly execute function, but when I split it into several smaller ones, game ran very smoothly.
I am still unsure of the reason. It may be some bug in the engine, or perhaps I just optimized the algorythm without paying enough attention to how much it improved the game speed.
IIRC Chris Jones mentioned something like this too...
Title: Re: optimizing loop
Post by: miguel on Thu 17/01/2013 17:14:49
QuoteIs this something that you see in practice?
Yes, at least to my limited knowledge. Splitting the code into small but objective functions allowed me to run all the stuff happening without the player being blocked. It was my main goal when I started this little exercise. A big rep_execute with dozens of if's would have made me quit half way. Well, if you didn't explain me the ropes I would have quit either way.
I believe that I can brake it even more and will do it.
And of course none of my functions include any complex algorithms, they are but simple while loops and such.
This is what my rep_exe_alws looks like: 

Code (AGS) Select
function repeatedly_execute_always()
{
  if (gamestart==true) {
  if (mood==free) {
    Label11.Text="free";
    enemydetection();
  }
  if (mood==ataking) {
    Label11.Text="atacking";
    colision1();
  }
  if (mood==fighting) {
    Label11.Text="fighting";
    combat();
  }
  if (mood==taking) {
    badtakedamage();
  }
  if (mood==giving) {
    herotakedamage();
  }
  if (mood==heropushed) {
    hpushed();
  }
  if (mood==banditpushed) {
    bpushed();
  }
erasebadguys();
showinterface();
checkdeath();
kill();
  }
}


There's still room for more function simplification.
All this time I thought that this was the correct way to do it, but
QuoteI would not say anything like that for any programming language
made me realize how specific AGS scripting is.
Title: Re: optimizing loop
Post by: Khris on Thu 17/01/2013 19:34:24
Miguel, splitting rep_exe code into several functions won't prevent blocking. Either code is blocking or it isn't; it doesn't matter whether it is in its own function or not.
I'm fairly surprised about the speed thing though; my guess is that AGS's memory management comes into play here.
Title: Re: optimizing loop
Post by: miguel on Thu 17/01/2013 20:31:52
Khris, I wouldn't dare defy your expertise, it's obvious that I now very little of this. I did think I got it working doing it the way I did.
Consequentially my error reminded Crimson of that "speed" enhancement.

Anyway, you do agree that breaking the code into functions allows to organize things better, no? I understand that rep_exe is a chain of events that continuously loop and splitting code into functions and then call them back into the loop is the same thing. But, "my method" should make things slower, right?

Question here: if everything runs inside the loop and all is coded in rep_exe, calling return to stop a function would also break the chain? If done on functions outside rep_exe, it would go back to the following chain function? Would the outcome be exactly the same?
 
I'm learning here, and more questions come up with every change I make to the code.
Title: Re: optimizing loop
Post by: Khris on Thu 17/01/2013 21:04:34
I'm all for breaking things up into tidy chunks, absolutely, and I'm glad that the weird speed difference favors splitting things up. It would be really bad if we had to choose between tidy code and fast execution.
I'm not sure exactly what you're referring to by "my method", if you're talking about calling several functions in rep_exe, it seems it doesn't make things slower, on the contrary.

As for "return", calling it will exit only the current function, not the one that was calling it. If that would be the case, what if you called a function that called a function? Based on what would AGS be supposed to decide how many functions to exit?

What's contributing to this confusion is some people having posted code in the past that made heavy use of return in order to prematurely exit a function. While it is legitimate to use it that way, the original purpose of the return function is returning a value, the "value of the function" as it were.
Here's two examples:
Code (ags) Select
int Sum(int a, int b) {
  return a + b;
}

int Abs(int a) {
  if (a < 0) return -a;
  return a;
}


The first one shows the proper use of return; the function does something to the parameters and returns the outcome, in this case an int. The data type of this outcome is specified by the function type, before the function's name. We're defining a function that returns an int, based on two int parameters.
The second function returns the absolute value. Here, in the case of a being less than 0, the function is exited prematurely, but still returning the correct value.

Whenever you use a function in code, it is executed, then replaced by it's return value. Take a look at this example:
Code (ags) Select
int mySum = 3 + Sum(4, 5);
Here, the return value is used in a mathematical term. It evaluates to 3 + 9.

If we're talking about functions that just do something and don't return anything, like for example UpdateActionBar(), return can be "abused" to exit the function.
Execution will resume in the line after the function call.
Title: Re: optimizing loop
Post by: Crimson Wizard on Thu 17/01/2013 21:49:58
Quote from: Khris on Thu 17/01/2013 21:04:34
If we're talking about functions that just do something and don't return anything, like for example UpdateActionBar(), return can be "abused" to exit the function.
There's a code style that suggests using early 'return' instead of taking a different execution branch(es) into 'if' block, like
Code (ags) Select

if (nothing_else_matters)
   return;

lots of code here

instead of
Code (ags) Select

if (!nothing_else_matters)
{
   lots of code here
}

Good or bad, I use that a lot :P.
On other hand, the restriction to only one return may force coder to reorganize function contents, so that there is no long trailing pieces of code under 'ifs'.
Title: Re: optimizing loop
Post by: miguel on Thu 17/01/2013 22:30:04
Quoteif you're talking about calling several functions in rep_exe
- yes that's what I meant.
About the speed, I wonder if anybody can back this behaviour with similar experiences.

But, you're saying that in a big if with several if's, when return (with no return value, thanks for the tutorial :smiley:) is called, it exits the if condition but stays on the one where it was first included?
Like (not real code):

Code (AGS) Select
if (a > b) {
  if (b==3) {...code
    if (d==b) {...code...; return;
    }
  }
  if (b==4) {...code
  }
}


When return is called the code goes back to if (b==3) {...code?
-------------------------------------------------------------------------------

Crimson, I was about to post this when I noticed your post, this is becoming interesting!
Title: Re: optimizing loop
Post by: Crimson Wizard on Thu 17/01/2013 22:36:05
Miguel, the return statement exits the current function right away. It has nothing to do with ifs / elses / loops. Just function.
Title: Re: optimizing loop
Post by: miguel on Thu 17/01/2013 22:47:56
Okay, I understand that,
but when a function is inside a if?
Title: Re: optimizing loop
Post by: Crimson Wizard on Thu 17/01/2013 22:52:08
The return statement moves the execution out of the current function to the line of code after function call.
It does not matter where function call was.

Each function has an "invisible" return in the end anyway, so makign a return from the middle of the function will continue program exactly as if the function returned "normally". It does not matter if there was if/else/loop etc.

Code (ags) Select

if (....)
{
   my_func(); // <---- calling the function
   some other code // <---- this is where program continues after return from function, in ANY way.
}



//-------------
EDIT:
Wait, do you mean, if there's an if INSIDE the function?
In this case, it will just return as always.
Code (ags) Select

function my_func()
{
   if (a)
   {
      if (b)
      {
         return; // exits the function right away, no more code from this function is executed
      }
      return; // same
   }

  // hidden "return" here, exits the function
}
Title: Re: optimizing loop
Post by: miguel on Thu 17/01/2013 23:01:00
Thanks, now I understand it.
It turns out I did the mistake of using return where it should not be used, a old and bad habit of wrapping things up with return...
This "lessons" were most helpful to me.
Title: Re: optimizing loop
Post by: Khris on Thu 17/01/2013 23:18:26
Quote from: Crimson Wizard on Thu 17/01/2013 21:49:58There's a code style that suggests using early 'return' instead of taking a different execution branch(es) into 'if' block
Good or bad, I use that a lot :P.
Me too :)
Title: Re: optimizing loop
Post by: miguel on Tue 22/01/2013 13:33:51
Back again!

I decided to change the behaviour of my enemies when chasing the player. I was using follow character before.
After the change, enemies "walk" without leaving the spot.
Here is the code, you can see where I changed:

Code (AGS) Select
function enemydetection() {
  int closestEnemy = -1;
  int maxRange = 700;
  int xDiff; int yDiff; int enemyDistance;
  int i=1;
  while (i< 7) {
  xDiff = stHero.Char.x - stbandit[i].Char.x;
  yDiff = stHero.Char.y - stbandit[i].Char.y;
  enemyDistance = xDiff*xDiff + yDiff*yDiff;
  if (enemyDistance < maxRange) {
    if (stbandit[i].health > 0) {
      mood=ataking;
//    stbandit[i].Char.FollowCharacter(stHero.Char, 0, 0);
    stbandit[i].Char.Walk(stHero.Char.x, stHero.Char.y, eNoBlock, eWalkableAreas);
    closestenemy=i;
//    return;
    }
  }
  else if (enemyDistance > maxRange) {
    mood=free;
    closestenemy=0;
//    stbandit[i].Char.FollowCharacter(null);
    stbandit[i].Char.StopMoving();
  }
i++;
  }
}


mood set to attacking runs a collision function, and set to free runs the function above;
Title: Re: optimizing loop
Post by: Khris on Tue 22/01/2013 16:15:21
Calling Character.FollowCharacter() changes the character's follow data, and that data is used by AGS to control the character, independently of how often it changes.
Character.Walk on the other hand is an "active" command which does something to the character directly.

While calling the first one 40 times per second works fine, calling .Walk every frame doesn't give the character any chance to actually start walking.
The solution is to add a check for Character.Moving:
Code (ags) Select

  if (!stbandit[i].Char.Moving) stbandit[i].Char.Walk(...);


The problem with this method: the character won't actually follow the player, they'll just move wherever the player used to be. If you want to stick to this method, you might want to call the .Walk command every few seconds.
I'd add a timer int to the bandit struct and count it down, then call .Walk again.
Title: Re: optimizing loop
Post by: miguel on Tue 22/01/2013 17:50:04
Code (AGS) Select
if (!stbandit[i].Char.Moving) stbandit[i].Char.Walk(...);

I should have known this bit by now. Crimson advice was the same when he told me about checking if a character was animating and so on.
Enemies not following the player is not really a problem as long as they meet him and collide. The problem I'm facing now is that when the fight occurs it gets engaged super fast. I tried to counter this with a function that "pushed" character away a few pixels if they are hit:

Code (AGS) Select
function bpushed() {
  int i=closestenemy;
  stbandit[i].Char.FollowCharacter(null);
  if (stbandit[i].name=="Bandit") {
  if (stbandit[i].Char.x > stHero.Char.x) {
    if (!stbandit[i].Char.Moving) {
    stbandit[i].Char.Move(stbandit[i].Char.x + 8, stbandit[i].Char.y, eNoBlock, eWalkableAreas); 
    AudioChannel* bhurt = abandit_hurt.Play();
    }
  }
  if (stbandit[i].Char.x < stHero.Char.x) {
    if (!stbandit[i].Char.Moving) {
    stbandit[i].Char.Move(stbandit[i].Char.x + 8, stbandit[i].Char.y, eNoBlock, eWalkableAreas); 
    AudioChannel* bhurt = abandit_hurt.Play();
    }
  }
  }
  if (stbandit[i].name=="Rat") {
    if (stbandit[i].Char.x > stHero.Char.x) {
    if (!stbandit[i].Char.Moving) {
    stbandit[i].Char.Move(stbandit[i].Char.x + 8, stbandit[i].Char.y, eNoBlock, eWalkableAreas); 
    AudioChannel* rpain = arat_pain.Play();
    }
  }
  if (stbandit[i].Char.x < stHero.Char.x) {
    if (!stbandit[i].Char.Moving) {
    stbandit[i].Char.Move(stbandit[i].Char.x + 8, stbandit[i].Char.y, eNoBlock, eWalkableAreas); 
    AudioChannel* rpain = arat_pain.Play();
    }
  }
  }
  mood=death;
}


My goal was to prevent characters from always colliding and it worked fine (not 100%) when I was using followcharacter; now the fights run super fast even if the "push" functions are being called.
Is the answer adding more checks to slow things down?
Title: Re: optimizing loop
Post by: Khris on Tue 22/01/2013 21:12:10
First of all, the only thing that changes in that code is the sound.
Also, you're adding 8 to the character's x in both cases.

Code (ags) Select
function bpushed() {
    int i=closestenemy;
    Character* c = stbandit[i].Char;
    c.FollowCharacter(null);

    AudioClip* hurt;
    if (stbandit[i].name == "Bandit") hurt = abandit_hurt;
    if (stbandit[i].name == "Rat") hurt = arat_pain;

    int dx;
    if (c.x > stHero.Char.x) dx = 8;
    else dx = -8;

    if (!c.Moving) {
        c.Move(c.x + dx, c.y, eNoBlock, eWalkableAreas);
        hurt.Play();
    }
    mood=death;
}
Title: Re: optimizing loop
Post by: miguel on Tue 22/01/2013 22:03:41
Wow! Take that Miguel! :) 
Strange that it kind of worked with followcharacter instead of enemies walking to player.x and y. Must be related to what you said about followcharacter.
Thanks for the help Khris, I guess I've got a lot to clean up in my code.