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.
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"?
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.
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 :).
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.
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:
enum EnemyState
{
eStateRunning,
eStateAttacking,
...etc
}
Then you declare variable as
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.
Got it, I'll dive into it right now.
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?
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?
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.
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:
// 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.
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.
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.
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?
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...
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:
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.
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.
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.
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:
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:
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.
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
if (nothing_else_matters)
return;
lots of code here
instead of
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'.
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):
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!
Miguel, the return statement exits the current function right away. It has nothing to do with ifs / elses / loops. Just function.
Okay, I understand that,
but when a function is inside a if?
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.
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.
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
}
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.
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 :)
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:
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;
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:
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.
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:
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?
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.
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;
}
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.