Porting game to MonoAGS

Started by cat, Sun 13/05/2018 21:12:08

Previous topic - Next topic

cat

Hi!
I want to port Cornelius Cat from AGS to MonoAGS. It is a fairly short game and has neither save/load nor translations, which is not available in MonoAGS, yet.
I thought I'd create a new thread for this, now that there is a separate board, to keep things organised.

I'm currently trying to set up the basic game structure (using mostly the Demo game and a bit of CW's games) and already have the first questions:

Is a player character needed, because it is loaded before the splash screen?

The splash screen (loadSplashScreen) has nothing to do with the Splash asset, right? It is the AGSSplashScreen from the engine but there does not seem to be documentation for it?

Crimson Wizard

#1
Quote from: cat on Sun 13/05/2018 21:12:08
Is a player character needed, because it is loaded before the splash screen?

No, you may make a game without characters at all (none of my games have any).
Changing rooms is done using Game.State.ChangeRoomAsync(room), so it is not directly related to character's going there, unlike AGS.

I am not sure how player character's position is synchronized with changing the room, haven't investigated that yet. Probably you may set Game.State.Player, but idk if it changes room automatically after the character.

tzachs

Quote from: cat on Sun 13/05/2018 21:12:08
The splash screen (loadSplashScreen) has nothing to do with the Splash asset, right? It is the AGSSplashScreen from the engine but there does not seem to be documentation for it?
What do you mean by "splash asset"?
Yes, sorry, the splash screen is not currently documented, and is very basic for now. It's basically a blank room with a loading text that tweens its scaling until you tell it to move to another room.
You can change the text by setting the "LoadingText" property.
You can change the font/text color/outline/shadow/etc by setting the "TextConfig" property.
And when you call the load function on the splash screen you get back a reference to your room so you can set a background or add more stuff to the room (like any other room).
Here's an example which changes the text, changes it to a blue color and adds a background image:

Code: csharp

var mySplashScreen = new AGSSplashScreen();
mySplashScreen.LoadingText = "My Loading Text";
mySplashScreen.TextConfig = new AGSTextConfig(brush: game.Factory.Graphics.Brushes.LoadSolidBrush(Colors.Blue));
var room = mySplashScreen.Load(game);
room.Background = myBackgroundImage;
await game.State.ChangeRoomAsync(room);

...
//Load stuff here
...

await game.State.ChangeRoomAsync(firstGameRoom);


Quote
I am not sure how player character's position is synchronized with changing the room, haven't investigated that yet. Probably you may set Game.State.Player, but idk if it changes room automatically after the character.
If you change the player character's room it automatically changes the room in the game state as well, so the behavior matches what you expect when you change the player's position in AGS, but like CW said, you don't need to have a player character as you can just change the room in the state (which won't move the player character to that room). I think I set the player first because at the beginning I had the exact AGS behavior so the player was mandatory, but at some point I changed it.

cat

Thanks for the explanation
Quote from: tzachs on Mon 14/05/2018 00:44:36
What do you mean by "splash asset"?
In the assets folder->rooms there is a splash screen, but I don't think it is ever used.

One reason for this thread is to point out stuff that is either confusing in the demo game, needs more documentation or should be improved.



Monsieur OUXX

Itis ndeed not used, however that's not the important thing about the Splashscree room.
the important thing is that it has a different structure from the other rooms. It is designed to have an unpredictable framerate, which means that onRepeatelyExecute and the tweens update are called manually from this room's loading function itself.
If you don't feel too confortable with that weird room, I'd say that you may skip the splash screen altogether and just go directly to an actual room. It will take a few seconds to load, but for a small game it's no big deal.


 

Crimson Wizard

#5
Quote from: Monsieur OUXX on Mon 14/05/2018 15:08:23
If you don't feel too confortable with that weird room, I'd say that you may skip the splash screen altogether and just go directly to an actual room. It will take a few seconds to load, but for a small game it's no big deal.

Don't remember if I mentioned this before, but IMHO DemoGame is not the best example of resource management, because it loads everything at the start (last time I checked), hence the need for "loading" screen. You may write a trivial resource manager which loads stuff for each room only when game is going there, that would reduce waiting time significantly.

Since we are at that, a small warning: MonoAGS currently does not support audio streaming (playing & loading at the same time), for that reason loading audio clips takes longer than in AGS.

cat

Quote from: Monsieur OUXX on Mon 14/05/2018 15:08:23
If you don't feel too confortable with that weird room, I'd say that you may skip the splash screen altogether and just go directly to an actual room. It will take a few seconds to load, but for a small game it's no big deal.
I'm comfortable enough to use it, I just want to point out issues that may confuse other game developers. Like the missing documentation or the unused graphic in the assets folder.

tzachs

Added a doc for the splash screen and added the splash background asset to the splash screen in the demo game so it's being used now.

cat

I pulled the latest version but I get an exception when starting DemoQuest.Desktop at AGS.Engine.FileSystemResourcePack.autoDetectAssetsFolder line 104

System.ArgumentException: 'URI formats are not supported.'

tzachs

I have a fix for this which I'll hopefully push tonight.
Meanwhile you can work around it by passing the path to your assets folder to the constructor of FileSystemResourcePack which bypasses the auto detection code.

I.e change the line here: https://github.com/tzachshabtay/MonoAGS/blob/f7be8974648591740752d2c3be23ac2ea7990a4a/Source/Demo/DemoQuest/Program.cs#L23

to:

Code: csharp

string pathToMyAssetsFolder = "c:\cat\game\Assets";
game.Factory.Resources.ResourcePacks.Add(new ResourcePack(new FileSystemResourcePack(AGSGame.Device.FileSystem, AGSGame.Device.Assemblies.EntryAssembly, pathToMyAssetsFolder), 0));

cat

No worries, I spent the evening learning about async/await (https://docs.microsoft.com/en-us/dotnet/csharp/async). It a bit mind-bending because I'm used to JS promises, but I think I start to understand it.

tzachs

#11
Cool, javascript has async/await too now, btw (they copied from c# and f#): https://javascript.info/async-await

Anyway, in case it's helpful, here's a really quick primer for AGSers on async/await:

AGS:     cEgo.Walk(eNoBlock)
MonoAGS: cEgo.WalkAsync()

AGS:     cEgo.Walk(eBlock)
MonoAGS: await cEgo.WalkAsync()

AGS:     cEgo.Walk(eNoBlock); /* Do stuff */ while (cEgo.IsMoving) Sleep(1);
MonoAGS: var walkTask = cEgo.WalkAsync(); /* Do stuff */ await walkTask;

This 3rd scenario is where async/await starts to add value. For walk you can check IsMoving on AGS, but for "say background" you'll need IsSpeaking, and you'll need more APIs for each async (non blocking) operation, whereas the API is always going to be the same with async/await. And it's not even going to be perfectly accurate in the AGS way of doing things, as while you were sleeping it might be possible (in case you have multiple threads) for the character to walk again because another command told the character to walk so you'll keep waiting. With the async/await scenario you know you're waiting for YOUR walk command, so you can't have this problem.

And where it really starts to shine is when you chain multiple async/await in whatever way you want.
For example, imagine a guard walking in the background between 4 different points in an endless loop.
In AGS, I would write something like this:

Code: ags

onRepeatedlyExecute()
{
    if (cGuard.X == 0 && cGuard.Y == 0 && !cGuard.IsMoving) cGuard.Walk(50, 0, eNoBlock);
    else if (cGuard.X == 50 && cGuard.Y == 0 && !cGuard.IsMoving) cGuard.Walk(50, 50, eNoBlock);
    else if (cGuard.X == 50 && cGuard.Y == 50 && !cGuard.IsMoving) cGuard.Walk(0, 50, eNoBlock);
    else if (!cGuard.IsMoving) cGuard.Walk(0, 0, eNoBlock);
}


In MonoAGS:

Code: csharp

async void guardWalkLoop() //Using "async void" to let people know I'm a "fire and forget" method. If I want whoever calls me to wait for me to finish, I'd change the signature to "async Task" (but then I can have a "while true" in the method)
{
    while (true)
    {
        await cGuard.WalkAsync(50, 0);
        await cGuard.WalkAsync(50, 50);
        await cGuard.WalkAsync(0, 50);
        await cGuard.WalkAsync(0, 0);
    }
}


Now imagine you want the guard to say something between a few of the points, or if you want to change routes, of have multiple guards walking in parallel but occasionally need to sync with each other, it gets really hard in AGS and is trivial with async/await.

cat

Nice! I haven't been coding for a year now (and at work I was limited to older script versions) so it's about time for me to get up-to-date :)

tzachs

Quote from: cat on Tue 15/05/2018 20:12:05
I pulled the latest version but I get an exception when starting DemoQuest.Desktop at AGS.Engine.FileSystemResourcePack.autoDetectAssetsFolder line 104

System.ArgumentException: 'URI formats are not supported.'

I pushed a fix for this, can you pull again and retry?
Thanks.

cat

#14
Yes, it's working now.

Edit: And another question about LoadOutfitFromFolders:
I see in the demo game that only speakLeftFolder for talk and walkLeftFolder and walkDownFolder for walk are used. How does the engine decide which one to use for what, e.g do I always have to provide speakLeftFolder when I want to have the same animation for all talk animations or could I also use speakRightFolder? This is missing in the code comments/documentation (or did I just not find it?)

tzachs

Quote from: cat on Wed 16/05/2018 20:01:52
Edit: And another question about LoadOutfitFromFolders:
I see in the demo game that only speakLeftFolder for talk and walkLeftFolder and walkDownFolder for walk are used. How does the engine decide which one to use for what, e.g do I always have to provide speakLeftFolder when I want to have the same animation for all talk animations or could I also use speakRightFolder? This is missing in the code comments/documentation (or did I just not find it?)

Yes, you can use speakRightFolder instead of speakLeftFolder (in general, the engine will try to use the closest directional animation it has). Documentation mentions it here.

And predicting a potential next question, yes, it currently does support only 8 directions, should add support to 360 (or rather, infinite because it will probably be a float) possible directions at some point (and we'll still have shortcuts to the 8 directions -> up will point to 0, down to 180, etc) and we'll probably also want explicit support for top-down views which will just rotate the sprite.

cat

Ok, I finally got the first background shown. I'm still not done with setup, but I want to find a way to make it cleaner than in the demo game.

I have basically just one huge scrolling background for the game and a few questions:

  • It's just one background, no objects, music and stuff - yet, the loading screen is shown for a full second or so. Why does it take so long? Is it because of the big background?
  • When the background is shown, after fade in, it quickly pans to the location which is undesired. Is this done automatically? I think it should fade directly to the position where I call change room. Or is it something different here?
  • I couldn't find documentation for Character.ChangeRoomAsync, especially how the coordinates work (i.e. where is 0,0)
  • It's hard to find/understand errors that occur. When resources for the walkcycle were not there, loading wouldn't work and I was stuck on splash screen, I only found the cause of the error when stepping through initialization with the debugger.
  • My character is not visible yet. Do I have to do something special to show it? I changed the room and the screen is on the correct position.
  • I tried to say something after the room change, but the game crashes on me:
    System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.' at AGS.Engine.OpenGLBackend line 30.

Here is my init code:
Code: ags

private async Task loadGameData(IGame game)
        {
            Debug.WriteLine("Startup: Initializing Splash Screen");
            IRoom splashScreen = new AGSSplashScreen().Load(game);
            game.State.Rooms.Add(splashScreen);
            splashScreen.Events.OnAfterFadeIn.SubscribeToAsync(async () =>
            {
                await Rooms.LoadAsync(game);
                await Characters.LoadAsync(game);
                game.State.Player = Characters.Cornelius;
                await game.State.Player.ChangeRoomAsync(Rooms.Stage, 2000, 500);
                Characters.Cornelius.Say("Hello, here I am.");
            });
            await game.State.ChangeRoomAsync(splashScreen);
        }




tzachs

Would it be possible for you to share a copy of your project with me?
Without it it's going to be much harder to help.

Quote from: cat on Sat 19/05/2018 21:55:51
It's just one background, no objects, music and stuff - yet, the loading screen is shown for a full second or so. Why does it take so long? Is it because of the big background?
I don't know. How big is the background (file size)?
You can try commenting out the line that loads the background and comparing the time differences.
Also, if you feel up to the challenge, you can profile the loading time to see what's taking up time. There's a built in profiler when you debug in visual studio: https://docs.microsoft.com/en-us/visualstudio/profiling/beginners-guide-to-performance-profiling
(If you share your project, I'll profile it).

Quote from: cat on Sat 19/05/2018 21:55:51
When the background is shown, after fade in, it quickly pans to the location which is undesired. Is this done automatically? I think it should fade directly to the position where I call change room. Or is it something different here?
It should fade directly to the position when you change room. Are you changing the viewport positions manually? Or are you moving the character in "after fade in"? Because if you move the character after the room fades in then the camera will not change at once but will do a smooth movement so maybe that's what happenning?

Quote from: cat on Sat 19/05/2018 21:55:51
I couldn't find documentation for Character.ChangeRoomAsync, especially how the coordinates work (i.e. where is 0,0)
Yes, I need to add a doc page on all the co-ordinates systems in the engine.
(0,0) is the bottom left.
Docs for Character.ChangeRoomAsync is here (though there's not much there).
And you can access it directly from the ICharacter page.
A tip if you tried using the search: there's currently a bug in DocFX that the search misses things. You can work around it by searching for "ChangeRoomAsync*" instead of "ChangeRoomAsync".
Here's a link to the bug. If you (or anybody) wants to comment on it and maybe put some more pressure on them to fix it already it would be nice.

Quote from: cat on Sat 19/05/2018 21:55:51
It's hard to find/understand errors that occur. When resources for the walkcycle were not there, loading wouldn't work and I was stuck on splash screen, I only found the cause of the error when stepping through initialization with the debugger.
If you're running in "Debug" from visual studio there's printouts to the output tab. Did you look there for errors?
Otherwise, not sure, I'll need to reproduce and see what went on there.

Quote from: cat on Sat 19/05/2018 21:55:51
My character is not visible yet. Do I have to do something special to show it? I changed the room and the screen is on the correct position.
Besides having the character in the same room, you also need to start the idle animation (or any animation, or set an image). You just need to do it the first time, it will then transition back to idle automatically after walk animations.

Quote from: cat on Sat 19/05/2018 21:55:51
I tried to say something after the room change, but the game crashes on me:
System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.' at AGS.Engine.OpenGLBackend line 30.
Can you share the complete stacktrace of the exception?
Also, can you try changing from "Characters.Cornelius.Say("Hello, here I am.");" to "await Characters.Cornelius.SayAsync("Hello, here I am.");" and see if it helps?
If it doesn't help, you can also try doing the say (or say async) in the "after fade in" event of the "stage" room instead of the splash screen room and see if that works.

cat

#18
Quote from: tzachs on Sun 20/05/2018 06:06:37
Would it be possible for you to share a copy of your project with me?
Without it it's going to be much harder to help.
Sure, I'll send you a link. I know it would be easier to make a public github repo for it, I'm just not sure I really want to make it all public (not because of the code but the general copyright to the game and assets).

Quote
I don't know. How big is the background (file size)?
Actually, it's pretty big, ~3MB png. It's a high-res photo.
Quote
You can try commenting out the line that loads the background and comparing the time differences.
A bit faster, but not much. But today it seems to start quicker than yesterday. Who knows what Windows was doing yesterday (mining bitcoins? :P)
It's not a big issue anyway, I was just curious.

Quote
It should fade directly to the position when you change room. Are you changing the viewport positions manually? Or are you moving the character in "after fade in"? Because if you move the character after the room fades in then the camera will not change at once but will do a smooth movement so maybe that's what happenning?
I just did what is shown in the code I posted, i.e. set the character as player and then call ChangeRoomAsync

Quote
Docs for Character.ChangeRoomAsync is here (though there's not much there).
And you can access it directly from the ICharacter page.
A tip if you tried using the search: there's currently a bug in DocFX that the search misses things. You can work around it by searching for "ChangeRoomAsync*" instead of "ChangeRoomAsync".
Here's a link to the bug. If you (or anybody) wants to comment on it and maybe put some more pressure on them to fix it already it would be nice.
Ah, that's why I didn't find it in the doc. Btw, when searching for ChangeRoomAsync* only IHasRoomComponent is in the result, not ICharacter.

Quote
If you're running in "Debug" from visual studio there's printouts to the output tab. Did you look there for errors?
Otherwise, not sure, I'll need to reproduce and see what went on there.
Right, I checked again and I see a null reference exception in output. However, the program doesn't crash but nothing else happens and the game is stuck. When I press the pause button, execution seems to be in AGSGamesWindow at line 65 public void SwapBuffers() => _gameWindow.SwapBuffers(); I think it might be better to let the game crash (at least in debug mode) instead of being just stuck.

Quote
Besides having the character in the same room, you also need to start the idle animation (or any animation, or set an image). You just need to do it the first time, it will then transition back to idle automatically after walk animations.
Thanks, that did it.

Quote
Quote from: cat on Sat 19/05/2018 21:55:51
I tried to say something after the room change, but the game crashes on me:
System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.' at AGS.Engine.OpenGLBackend line 30.
Can you share the complete stacktrace of the exception?
Also, can you try changing from "Characters.Cornelius.Say("Hello, here I am.");" to "await Characters.Cornelius.SayAsync("Hello, here I am.");" and see if it helps?
If it doesn't help, you can also try doing the say (or say async) in the "after fade in" event of the "stage" room instead of the splash screen room and see if that works.
With SayAsync it does not crash but I also don't see text.
Spoiler

Stacktrace (local paths obscured)

at OpenTK.Graphics.OpenGL.GL.Clear(ClearBufferMask mask)
   at AGS.Engine.OpenGLBackend.ClearScreen()
   at AGS.Engine.AGSGame.onRenderFrame(Object sender, FrameEventArgs e) in <local>\CorneliusCat\MonoAGS\Source\Engine\AGS.Engine\Game\AGSGame.cs:line 203
   at AGS.Engine.Desktop.AGSGameWindow.onRenderFrame(Object sender, FrameEventArgs args)
   at System.EventHandler`1.Invoke(Object sender, TEventArgs e)
   at OpenTK.GameWindow.OnRenderFrame(FrameEventArgs e)
   at OpenTK.GameWindow.OnRenderFrameInternal(FrameEventArgs e)
   at OpenTK.GameWindow.RaiseRenderFrame(Double elapsed, Double& timestamp)
   at OpenTK.GameWindow.DispatchUpdateAndRenderFrame(Object sender, EventArgs e)
   at OpenTK.GameWindow.Run(Double updates_per_second, Double frames_per_second)
   at OpenTK.GameWindow.Run(Double updateRate)
   at AGS.Engine.Desktop.AGSGameWindow.Run(Double updateRate)
   at AGS.Engine.AGSGame.Start(IGameSettings settings) in <local>\CorneliusCat\MonoAGS\Source\Engine\AGS.Engine\Game\AGSGame.cs:line 142
   at CorneliusCat.GameStarter.Run() in <local>\CorneliusCat\CorneliusCat\GameStarter.cs:line 16
   at CorneliusCat.Desktop.Program.Main(String[] args) in <local>\CorneliusCat\CorneliusCat.Desktop\Program.cs:line 10
[close]

Btw, is there another way of setting the character outfit? I want to reuse some frames for an animation and not copy them several times in resources. I'd like to build the animation manually.

tzachs

I pushed fixes to master for both the camera panning issue and the character speaking.
Note that I removed Character.Say, SayAsync should be used instead, that's because the blocking say can't work in all scenarios (like in after fade in, because it's blocking it also blocks the completion of the event so you will not see it happening). If I return it (technically it's not needed, SayAsync can do everything Say can do) I'll need to think of a more sophisticated way for it.

Also, I saw that there's a bug with how the fade transition works in your loading screen (instead of fading in the new room, it's fading in the splash screen). Not yet sure what's going on there, it seems more complicated, I will look at it after I finish my current work.
For the mean time, if it bothers you as it bothers me, you can remove the fade transition from the game load event, and move it to after your first (non splash screen) room fades in. Note that this will also reduce the time of the splash screen (by default the fade transition is 1 second fade in and 1 second fade out).

Quote from: cat on Sun 20/05/2018 09:27:57
I know it would be easier to make a public github repo for it, I'm just not sure I really want to make it all public (not because of the code but the general copyright to the game and assets).
You can also create a private repository. It costs money on github, but it's free on both VSTS and BitBucket.

Quote
Btw, when searching for ChangeRoomAsync* only IHasRoomComponent is in the result, not ICharacter.
This is because ICharacter doesn't really contain ChangeRoomAsync, it's composed out of components (like IHasRoomComponent) which contain the actual behaviors.

Quote
Right, I checked again and I see a null reference exception in output. However, the program doesn't crash but nothing else happens and the game is stuck.
Can you describe what changes I need to make to reproduce this? Also, if possible can you share the stacktrace of the null reference exception?
Thanks.

Quote
Btw, is there another way of setting the character outfit? I want to reuse some frames for an animation and not copy them several times in resources. I'd like to build the animation manually.
Yes, you can.
An outfit is a collection of directional animations (idle/walk/etc), a directional animation is a collection of animations (left/right/etc), an animation is a collection of animation frames, each animation frame is sprite + more configurations for the frame (sound and delays), and a sprite is an image + more configurations for the sprite (rotation, scale).
So you can pick and choose at what level you want to share your resources, which dictates which factory method(s) to use: the graphics factory has methods to load directional animations, to load animations, to load images, and also to get empty sprites.

So here's an untested example that combines some of those together to create an outfit.
We'll load the idle directional animation directly from folders, we'll create the walking directional animation by loading a few animation folders ourselves and create the directional animation from it, we'll create the speaking directional animations by loading images, creating animations from them and add to a directional animation, and finally, we'll create a jump directional animation by taking the images from the idle directional animations and just shift their offsets a bit to do a (really silly) "programmatic" jump.

Code: csharp

var factory = game.Factory.Graphics;

var idle = await factory.LoadDirectionalAnimationFromFoldersAsync("Animations/Idle", "Left", "Down", "Right", "Up");

var walkLeft = await factory.LoadAnimationFromFolderAsync("Animations/Walk/Left");
var walkDown = await factory.LoadAnimationFromFolderAsync("Animations/Walk/Down");
var walk = new AGSDirectionalAnimation { Left = walkLeft, Down = walkDown };

var speakLeftImage1 = await factory.LoadImageAsync("Animations/Speak/Left/1.png");
var speakLeftImage2 = await factory.LoadImageAsync("Animations/Speak/Left/2.png");
var speakLeftSprite1 = factory.GetSprite();
var speakLeftSprite2 = factory.GetSprite();
speakLeftSprite1.Image = speakLeftImage1;
speakLeftSprite2.Image = speakLeftImage2;

//I'm missing a factory method to create an empty animation, so using the resolver for this (for now):
var speakLeft = AGSGame.Resolver.Container.Resolve<IAnimation>();
speakLeft.Frames.Add(new AGSAnimationFrame(speakLeftSprite1) { Delay = 3});
speakLeft.Frames.Add(new AGSAnimationFrame(speakLeftSprite2) { MinDelay = 4, MaxDelay = 8});
var speak = new AGSDirectionalAnimation { Left = speakLeft };

var jumpLeft = AGSGame.Resolver.Container.Resolve<IAnimation>();
float yOffset = 0f;
foreach (var idleFrame in idle.Left)
{
    var sprite = factory.GetSprite();
    sprite.Image = idleFrame.Sprite.Image;
    sprite.Y = yOffset;
    yOffset += 0.5f;
    var jumpFrame = new AGSAnimationFrame(sprite) { Delay = idleFrame.Delay };
    jumpLeft.Frames.Add(jumpFrame);
}
var jump = new AGSDirectionalAnimation { Left = jumpLeft };

var outfit = new AGSOutfit();
outfit[AGSOutfit.Idle] = idle;
outfit[AGSOutfit.Walk] = walk;
outfit[AGSOutfit.Speak] = speak;
outfit["Jump"] = jump;


cat

Quote from: tzachs on Mon 21/05/2018 16:03:26
I pushed fixes to master for both the camera panning issue and the character speaking.
Nice! It's working now :)

Quote
Note that I removed Character.Say, SayAsync should be used instead, that's because the blocking say can't work in all scenarios
Right, better remove it than having a half-broken version.

Quote
Also, I saw that there's a bug with how the fade transition works in your loading screen (instead of fading in the new room, it's fading in the splash screen). Not yet sure what's going on there, it seems more complicated, I will look at it after I finish my current work.
For the mean time, if it bothers you as it bothers me, you can remove the fade transition from the game load event, and move it to after your first (non splash screen) room fades in. Note that this will also reduce the time of the splash screen (by default the fade transition is 1 second fade in and 1 second fade out).
Wow, it's really much quicker that way.

Quote
You can also create a private repository. It costs money on github, but it's free on both VSTS and BitBucket.
Thanks, I'll think about it.

Quote
This is because ICharacter doesn't really contain ChangeRoomAsync, it's composed out of components (like IHasRoomComponent) which contain the actual behaviors.
I understand that, but the whole interface composition will be too complicated for the regular game developer. The documentation needs to have an article where everything you can do with a character is explained in the style of the old AGS doc.

Quote
Quote
Right, I checked again and I see a null reference exception in output. However, the program doesn't crash but nothing else happens and the game is stuck.
Can you describe what changes I need to make to reproduce this? Also, if possible can you share the stacktrace of the null reference exception?
Thanks.
For example, during character creation, I assign an outfit only for left:
IOutfit outfit = await game.Factory.Outfit.LoadOutfitFromFoldersAsync(_baseFolder, walkLeftFolder: "Walk", speakLeftFolder: "Walk", idleLeftFolder: "Walk");
and then I start the idle animation for down (because I didn't pay attention):
_character.StartAnimation(_character.Outfit[AGSOutfit.Idle].Down);
The problem is, the game gets stuck on the loading screen and the exception is only visible in output.

Quote
An outfit is a collection of directional animations (idle/walk/etc)...
Thanks! It would be great to have a shortcut, like factory.LoadSpriteFromFile or similar. Especially when doing multiple animations manually, it creates lots of code atm. Can I safely reuse a sprite for different frames or should I create/clone a new one?
Oh well :(. I tried creating the animation manually but didn't get it to work, yet. The viewport jumps weirdly and I just have flickering and no proper animation. I need to investigate further.


One more (not exactly MonoAGS) issue I have: I installed the VS Community Edition, which I thought was free? But now VS says that my 30 days trial license will expire in a few days ???

Monsieur OUXX

#21
Quote from: cat on Mon 21/05/2018 20:45:47
One more (not exactly MonoAGS) issue I have: I installed the VS Community Edition, which I thought was free? But now VS says that my 30 days trial license will expire in a few days ???

Sometimes the installer is the same but it's the license that matters. Check in google if there isn't a thing like "get your VS community key" which might lead you to a page where all you have to do is to provide your (any) email address to get the key.
Nowadays most of the time you can even generate a key directly from within VS. Chek ou the "help" or "tools" menu.
 

tzachs

Quote from: cat on Mon 21/05/2018 20:45:47
I understand that, but the whole interface composition will be too complicated for the regular game developer. The documentation needs to have an article where everything you can do with a character is explained in the style of the old AGS doc.
The character page has everything you can do with the character in link format, so it's similar to the AGS doc except for the link itself does not to point to the same page. I'm not sure there's an easy way to copy the link contents to the character page, I'll need to get a deeper understanding of how the documentation is auto generated. Hopefully I'll get to that at some point.
I reported the new scenario for the broken search, and they're investigating it, so hopefully it will improve the situation (and interestingly if you search for "*ChangeRoomAsync*" it does work).
Also note that I plan to have the documentation for the interactions available from within the (yet to be written) interaction editor (so each interaction will have a "?" button next to it or something like that).


Quote
Thanks! It would be great to have a shortcut, like factory.LoadSpriteFromFile or similar.
Done ("LoadSprite" and "LoadSpriteAsync").

Quote
Can I safely reuse a sprite for different frames or should I create/clone a new one?
It depends. If you share the sprite it means you also share the sprite's configuration (rotation, scaling, translation offset, pivot, tinting, brightness). In other words, if you use the same sprite on 2 animation frames, and you rotate the sprite in one of the frames it will also rotate it for the other animation frame. If that's what you want, or, if you don't intend to change configurations for individual sprites (and if you intend to port the game from AGS as is without changes, then you don't) you can safely share the sprites. But if you want independent configurations, then use different sprites (but they can still share the same image, which is safe).

Quote
One more (not exactly MonoAGS) issue I have: I installed the VS Community Edition, which I thought was free? But now VS says that my 30 days trial license will expire in a few days ???
It is free, but you need to sign in, see here: https://www.visualstudio.com/vs/support/community-edition-expired-buy-license/

cat

Quote from: tzachs on Tue 22/05/2018 15:00:43
Done ("LoadSprite" and "LoadSpriteAsync").
Already saw it this afternoon and used it in my code - thanks :)

Quote
Quote
One more (not exactly MonoAGS) issue I have: I installed the VS Community Edition, which I thought was free? But now VS says that my 30 days trial license will expire in a few days ???
It is free, but you need to sign in, see here: https://www.visualstudio.com/vs/support/community-edition-expired-buy-license/
Ah, I thought it might be something like that :-\ Well, can't complain too much about a free product. I assume when the MonoAGS editor is finished, people will not need VS installed, right?

tzachs

Quote from: cat on Tue 22/05/2018 17:19:59
Ah, I thought it might be something like that :-\ Well, can't complain too much about a free product. I assume when the MonoAGS editor is finished, people will not need VS installed, right?
Technically, you don't need VS even now. Any IDE/text editor which can compile c# code can be used (in theory, at least).
Once the first version of the MonoAGS editor will be released, you should be able to use any text editor even if it can't compile c# yourself as the editor will be able to compile the code. However, you'll probably still want to use an IDE for debugging purposes.
Then I'll look into the option of bundling the editor with an existing IDE (we previously talked about either MonoDevelop or VS Code as potential candidates), and also of having the editor as an add-in to VS itself (so 3 flavors: standalone, standalone bundled with text editor and debugger, add-in to VS -> hoping it wouldn't be too big a burden, otherwise we'll need to make a choice and cut).

cat

I think an editor without the need to register an account somewhere is a must.

tzachs

Quote from: cat on Mon 21/05/2018 20:45:47
For example, during character creation, I assign an outfit only for left:
IOutfit outfit = await game.Factory.Outfit.LoadOutfitFromFoldersAsync(_baseFolder, walkLeftFolder: "Walk", speakLeftFolder: "Walk", idleLeftFolder: "Walk");
and then I start the idle animation for down (because I didn't pay attention):
_character.StartAnimation(_character.Outfit[AGSOutfit.Idle].Down);
The problem is, the game gets stuck on the loading screen and the exception is only visible in output.
This should be fixed now (the game will properly crash with an error message).

Quote
Also, I saw that there's a bug with how the fade transition works in your loading screen (instead of fading in the new room, it's fading in the splash screen). Not yet sure what's going on there, it seems more complicated, I will look at it after I finish my current work.
This should also be fixed now.

Monsieur OUXX

For the record I've completely given up on this. I lost steam while writing Readers in C# for .xml files for rooms, characters, objects, etc.
Not MonoAGS' fault, though. I still think it has great potential. Just not mature enough for the use I want to make of it. If some sort of easy resources loader appeared, mirroring AGS' game.agf file (with sections split into several files) I'd give it another try.
 

tzachs

I figured, but thanks for the heads up.
My efforts are currently still targeted at having a functional working editor, and I'll update when I have something to show (going to take a while).

cat

For the record I've NOT given up on this, it's just a matter of time. I hope I'll find some time soon for setting up an account and getting my VS running again. I know it's not difficult, but I hate having to bother with this kind of stuff.

tzachs

Cool!
Depending on how desperate you are to avoid setting up an account (and how adventurous you are), you can try using another IDE.
MonoDevelop is free and open source and no registration is needed.
VSCode is another free and open source and no registration is needed, but you might need to work harder to make it work there (as it's general purpose, not c# specific like MonoDevelop, so you'll need to install a bunch of plugins and configure stuff).

Eventually I'll get to try those out myself (I have used MonoDevelop on Linux, actually, but did not try on Windows) and probably add a small tutorial for setting those up (if needed).

cat

Quoting the MonoDevelop homepage
QuoteMonoDevelop for Windows is available from source only
Please refer to the building guide for more information about how to install and configure your MonoDevelop.
The Prerequisites for building under Windows require Visual Studio with F# enabled :P

Why on earth can't they just provide a Windows installer?

tzachs

8-0
Facepalm.
Sorry, they had packages for linux and I assumed they would have an installer/downloadable for windows.

Quote from: cat on Thu 13/09/2018 19:06:37
Why on earth can't they just provide a Windows installer?
Yeah, this is bizarre ???. I'm guessing everybody just uses VS on Windows and their main audience is mac & linux, but still...
I opened an issue on their repo: https://github.com/mono/monodevelop/issues/5961

cat

Yeah, I mean building it myself is cumbersome enough but then also requiring VS is just weird. Since I don't do C# any more, I think I don't even have VS on my work PC.
Let's see if someone reacts to your issue. But I guess I'll get VS to run sooner or (more likely) later anyway.

Crimson Wizard

Quote from: tzachs on Thu 13/09/2018 19:30:32
Sorry, they had packages for linux and I assumed they would have an installer/downloadable for windows.

Quote from: cat on Thu 13/09/2018 19:06:37
Why on earth can't they just provide a Windows installer?
Yeah, this is bizarre ???. I'm guessing everybody just uses VS on Windows and their main audience is mac & linux, but still...
I opened an issue on their repo: https://github.com/mono/monodevelop/issues/5961

Lol, this looks like Linux users are trolling Windows users by not requiring to build from source on Linux and requiring to do so on Windows on contrary. Maybe they are angry on Microsoft for some reason?

cat

In the meantime, I tried SharpDevelop, but this seems to be too old, it can't even load the projects.

cat

I've set up an account and can run VS again now.
When I now pull the latest master, I get a few error messages:
Code: ags

Error	CS0535	'GameStarter' does not implement interface member 'IGameStarter.Settings
Error	CS0117	'AGSGame' does not contain a definition for 'CreateEmpty'
Error	CS0117	'AGSGameSettings' does not contain a definition for 'DefaultSpeechFont'
Error	CS0117	'AGSGameSettings' does not contain a definition for 'DefaultTextFont'	
Error	CS0117	'AGSGameSettings' does not contain a definition for 'DefaultSpeechFont'	
Error	CS0117	'AGSGameSettings' does not contain a definition for 'CurrentSkin'	


What do I have to change? Are there release notes or something?

tzachs

There are no official release notes, yet, I'm afraid (I haven't technically released anything yet). My current plan is to have a functional editor (which like I said, gonna take a while) and then release a v0.1 and start making organized releases and release notes from there on (I'm guessing that's the point the project will start looking more interesting to people).

You can however look at the list of the merged pull requests (I make sure to create pull requests for everything, so it should have all of the changes): https://github.com/tzachshabtay/MonoAGS/pulls?q=is%3Apr+is%3Aclosed
Each PR in itself has a list of commits which you can browse (some are more detailed than others...).
Though currently I haven't (unfortunately) made any efforts in listing breaking changes.

The errors you get are because of 2 breaking changes:
1. Made changes to how a game is started, this is to allow having 2 games that live side by side (as the editor is in itself a game which hosts your game).
2. Moved some defaults into their own class, which you can access from game.Settings.Defaults.

You can follow the structure in the demo game to make this changes: https://github.com/tzachshabtay/MonoAGS/blob/master/Source/Demo/DemoQuest/Program.cs

The relevant lines with changes are: 10, 17, 28-30, 44-51.

And if you're interesting in examining the commits that made these changes:
https://github.com/tzachshabtay/MonoAGS/commit/f58e4bfc633a225b26b77e50601ecc4af8d2a8cf
https://github.com/tzachshabtay/MonoAGS/commit/84050bcb7d49e6a0a8d9b14f22fe7f5a266959ca#diff-f3b30bc060275a41bac549848c305912

Let me know if you're having problems, I can make the changes for you.



cat

#38
Right, I got the program and the usage of dependency injection fixed. I can start the game and as a nice surprise, the custom font is now working (that didn't work before).

There are still other issues (that already were there before), but now that I can compile again, I'll have a look at them one after each other.

Oh, and I like the new point syntax and deconstruction. Gotta get used to the new language features :)

cat

Whoops, looks like I have the slot-machine version of adventure gaming. When I run the game from VS, I get randomly one of the following results:

  • The game runs just fine
  • It is stuck on loading screen
  • I get a "System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'" in OpenGLBackend line 30 (public void ClearScreen() => GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);)
    Spoiler
    System.AccessViolationException
      HResult=0x80004003
      Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
      Source=<Cannot evaluate the exception source>
      StackTrace:
    <Cannot evaluate the exception stack trace>
    [close]

tzachs

Oh, that doesn't sound good. 
Can you send me your project? I'll investigate.

cat

Not sure if anyone else is following this thread, but I've sent my code to tzachs. He investigated the issue and found a temporary workaround.

A question on a completely different topic:
For the original AGS game I used a GUI to represent the border around the stage. It's basically a non-clickable GUI covering the whole screen and the center is transparent. What would be the best way to implement this in MonoAGS?

tzachs

It depends.

1. If you already have an image for the border which is the size of the entire screen, then you can do the same.
The equivalent of "A GUI" is a panel which you can get from the GUI factory (it's already "click-through" by default):

Code: csharp

factory.UI.GetPanel("MyPanel", myImage, x, y);


Note that unlike GUIs in AGS, if you want the panel to only be associated with a specific room and not all rooms, you can write it that like this:

Code: csharp

var panel = factory.UI.GetPanel("MyPanel", myImage, x, y, addToUI: false);
myRoom.Objects.Add(panel);


2. If you don't have a specific image you want for your border, you can use the "Border" property and set a built-in border (or you can code your own custom border rendering, but that's more involved and the API is not stable currently, the way it's done is very likely to change in the future).
Built in borders include a solid color border, a gradient border or a 9-slice image border (9-slice image border is similar to the 9 images you set on a border in AGS for message boxes, only a little more fancy, similar to a css border image: https://css-tricks.com/almanac/properties/b/border-image/).
So to create a transparent panel with a solid color border, for example, you can write:

Code: csharp

var panel = factory.UI.GetPanel("MyPanel", width, height, x, y);
panel.Tint = Colors.Transparent;
panel.Border = factory.Graphics.Borders.SolidColor(Colors.Blue, lineWidth: 3f, hasRoundCorners: true);



cat

Ah, in this case I will try a panel.

cat

#44
The panel frame is working :)

Is it possible to define a mask myself without a file? I just need a rectangle for the walkable area. If not, no worries, I'll just draw a mask.

Oh, and since you are the moderator in this board: Do you prefer that I edit posts with additional info or should I create a new post to bump the thread?


Edit: I just realized, I can probably do it with GetBitmap and then CreateMask?
Edit2: Seemed to work! I cannot walk yet, but PlaceOnWalkableArea() moves the character to where the WA is supposed to be :)

tzachs

Quote from: cat on Tue 18/09/2018 21:02:01
Edit: I just realized, I can probably do it with GetBitmap and then CreateMask?
Edit2: Seemed to work! I cannot walk yet, but PlaceOnWalkableArea() moves the character to where the WA is supposed to be :)
Cool! Very happy you figured it out without any specific documentation :)
Another way (just for posterity, what you did sounds better) would have been to create "new AGSMask" and give it a 2d boolean array.

Quote from: cat on Tue 18/09/2018 21:02:01
I cannot walk yet
Ah, so I think your game uses left to interact and right to look, right?
I have an incomplete implementation of that scheme in: https://github.com/tzachshabtay/MonoAGS/blob/master/Source/Engine/AGS.Engine/Input/Schemes/TwoButtonsInputScheme.cs
Not sure if what's there is in a working state or not (haven't touched it in a while but from brief examination of the code walking at least should work), you can try it by writing:
Code: csharp

var scheme = new TwoButtonsInputScheme(game, game.Input);
scheme.Start();

If it's working you should be able to walk.

If you're feeling up for it, it should be relatively simple to complete the implementation (just hadn't gotten around to it yet).
It's basically just looking at how it's done in the rotating cursors scheme (https://github.com/tzachshabtay/MonoAGS/blob/master/Source/Engine/AGS.Engine/Input/Schemes/RotatingCursorScheme.cs) without the actual rotating cursors stuff.
If not, then I'll get to it soon enough, hopefully in the next few days.

Quote from: cat on Tue 18/09/2018 21:02:01
Oh, and since you are the moderator in this board: Do you prefer that I edit posts with additional info or should I create a new post to bump the thread?
Bumping is fine. If you edit there's a chance I won't see it. Also I'm susbcribed to the topic so I'm getting an email whenever you post and can give you a quicker response.

Monsieur OUXX

Quote from: cat on Thu 13/09/2018 19:06:37
Why on earth can't they just provide a Windows installer?
Because linuxians. They like to capture the souls of innocent passerbys and torture them until they have entirely drained the will to live from them. They take a specific perverted pleasure in doing so by providing tools meant for Windows but made on Linux and yet not compiled for Windows.

What do you mean, "slightly biased comment"?
 

cat

Quote from: tzachs on Tue 18/09/2018 22:33:24
Cool! Very happy you figured it out without any specific documentation :)
Another way (just for posterity, what you did sounds better) would have been to create "new AGSMask" and give it a 2d boolean array.
I used Bitmap.SetPixels() with a list I built using nested loops. Is there a better way?

Quote
Code: csharp

var scheme = new TwoButtonsInputScheme(game, game.Input);
scheme.Start();

When I use this and click somewhere, nothing happens in the game but I see an exception in the output window:
Exception thrown: 'System.IndexOutOfRangeException' in AGS.Engine.dll

tzachs

Quote from: cat link=topic=56055.msg636595179#msg636595179
I used Bitmap.SetPixels() with a list I built using nested loops. Is there a better way?
Well, if you only want a rectangle, then just getting an empty bitmap should be enough, I think: factory.Graphics.GetBitmap(width,height)

Quote
When I use this and click somewhere, nothing happens in the game but I see an exception in the output window:
Exception thrown: 'System.IndexOutOfRangeException' in AGS.Engine.dll
Ok, I'll look into it.

cat

I want a rectangle that doesn't cover the whole screen, only a small part.

cat

In the meantime, I added the first object and to make things interesting, it's an object with animation. Good news first: it works :)
I'm just having trouble positioning the object. Using the original coordinates from the AGS game does not work. How is the position determined? Is there a proper way to calculate from AGS to MonoAGS coords?

Crimson Wizard

#51
Quote from: cat on Sat 22/09/2018 21:00:02
I'm just having trouble positioning the object. Using the original coordinates from the AGS game does not work. How is the position determined? Is there a proper way to calculate from AGS to MonoAGS coords?

Y axis is turned other way, pointing up :)
Also, in MonoAGS object's image related to Position is determined by Pivot (or was it Anchor? I may not remember correct property's name).
So, if your AGS object was located at X,Y your MonoAGS object should be located at X, Y = AgsRoomHeight - agsY, plus some extra offset depending on pivot/anchor.

I made this function to port AGS scripts:
https://github.com/ivan-mogilko/MonoAGSGames/blob/master/Games/LastAndFurious/Rooms/RoomScript.cs#L57

cat

Thanks, that did it! So it's the other way round. And I didn't know I had to reset the pivot first. What is the reason for having it 0.5, 0 initially?

tzachs

The pivot point is by default 0.5,0 for characters and 0,0 for objects. The reason it's 0.5,0 for characters is that it makes walk cycle animations align even when the front/back frames are in a different width from the side animations. Also I find it to be more intuitive when setting walk areas.

Also sorry that I haven't been more helpful lately, currently I'm on a 3 week vacation and a bit low on free time with proper internet..

cat

Quote from: tzachs on Sun 23/09/2018 11:04:12
The pivot point is by default 0.5,0 for characters and 0,0 for objects.
Sorry I have to correct you, but at least with version a4b22 the default pivot point for objects is 0.5, 0. I looked at it via debugger and when I reset the pivot to 0,0 the object is moved to the correct position.
I agree about the character pivot point and reasoning, though.

Quote
Also sorry that I haven't been more helpful lately, currently I'm on a 3 week vacation and a bit low on free time with proper internet..
No worries, enjoy your vacation! I see what I can do in the meantime by myself. Also a good way to see how self-explanatory the documentation is ;)

Crimson Wizard

#55
I think in the end it may be worth to not rely on engine's defaults and have a set of utility functions that init objects of different kinds. For example in the racing game I've referenced above I had function that setup a racing car object and it sets pivot to (0.5, 0.5), because it lets you to trivially apply rotation around sprite center.

This reminds me, when I last saw (several months ago) both alignment and rotation was performed around pivot, and there was a suggestion to split properties into one determining position alignment and another for object's own transform operations:
https://github.com/tzachshabtay/MonoAGS/issues/284


UPD.
Also, the Y axis pointing up makes it inconvenient to work with GUI, since you have to position, for example, menu items vertically in opposite direction (first item having largest Y). I wrote a class for the game menu that does calculations internally to simplify things for myself:
https://github.com/ivan-mogilko/MonoAGSGames/blob/master/Games/LastAndFurious/GameMenuComponent.cs#L169

I think I was asking if it's possible to implement a surface with custom axis direction, but don't remember what was tzach's decision on that.

cat

Thanks Crimson. I'll have another close look at your repostitory, since you have answers to many of my questions already :)

tzachs

Quote from: cat on Thu 20/09/2018 21:36:31
When I use this and click somewhere, nothing happens in the game but I see an exception in the output window:
Exception thrown: 'System.IndexOutOfRangeException' in AGS.Engine.dll
I can't reproduce this, so I need more information.
Is this the entire error message, no additional stack trace? Also, where did you put this code? And can you also write the code you used to add the walkable area? Or alternatively, can you send me your project again (with the error reproducing)?

Quote from: cat on Thu 20/09/2018 21:36:31
I used Bitmap.SetPixels() with a list I built using nested loops. Is there a better way?
...
I want a rectangle that doesn't cover the whole screen, only a small part.
Not necessarily better, but you can get an empty bitmap as I said before, and then add a translate component which gives you the ability to "move" an area on the screen.
It will look something like this, for example:
Code: csharp

var bitmap = factory.Graphics.GetBitmap(1000, 1000);
var mask = factory.Masks.Load("walkablemask1", bitmap, true);
var area = factory.Room.GetArea("walkablearea1", mask, _room, true);
area.AddComponent<ITranslateComponent>().X = 2000f;


Quote from: cat on Sun 23/09/2018 11:12:52
Sorry I have to correct you, but at least with version a4b22 the default pivot point for objects is 0.5, 0. I looked at it via debugger and when I reset the pivot to 0,0 the object is moved to the correct position.
I agree about the character pivot point and reasoning, though.
Ah, right, I think I changed it to all images after realizing the same logic applies, you can have multiple animations with different widths and it makes it easier to align by default (and also it's more consistent). I don't necessarily have too strong opinions on this, willing to discuss if you have a better suggestion.

Quote from: Crimson Wizard on Sun 23/09/2018 13:29:00
I think in the end it may be worth to not rely on engine's defaults and have a set of utility functions that init objects of different kinds. For example in the racing game I've referenced above I had function that setup a racing car object and it sets pivot to (0.5, 0.5), because it lets you to trivially apply rotation around sprite center.
This makes sense for a racing game, but I wouldn't want the average user to have to create utility functions for a point&click game. The engine should be geared with defaults that apply to the most common use-cases for point&click (or in the future, like we discussed, we might have templates with different defaults, geared to that specific template use case).
I think cat's struggle here stems for the fact that she's porting an existing game, I don't think she'd encounter this issue when creating a new game (we'll see when people actually create new games, though).

Quote from: Crimson Wizard on Sun 23/09/2018 13:29:00
Also, the Y axis pointing up makes it inconvenient to work with GUI, since you have to position, for example, menu items vertically in opposite direction (first item having largest Y). I wrote a class for the game menu that does calculations internally to simplify things for myself:
https://github.com/ivan-mogilko/MonoAGSGames/blob/master/Games/LastAndFurious/GameMenuComponent.cs#L169
Check out the built in Stack layout component which solves this problem (at least for laying out menu items). You add your buttons to a panel, then you add the layout component to the panel and it automatically lays them out for you (and you can set the spacing between the items).

Quote from: Crimson Wizard on Sun 23/09/2018 13:29:00
This reminds me, when I last saw (several months ago) both alignment and rotation was performed around pivot, and there was a suggestion to split properties into one determining position alignment and another for object's own transform operations:
https://github.com/tzachshabtay/MonoAGS/issues/284
Right, the jury is still out on this one. I'm keeping the issue open, but as I said, I'm not sure the added flexibility here is worth the added complexity.

Quote from: Crimson Wizard on Sun 23/09/2018 13:29:00
I think I was asking if it's possible to implement a surface with custom axis direction, but don't remember what was tzach's decision on that.
Same here, it can be done and it adds flexibility, but I'm again not sure the added flexibility is worth the added complexity.

Crimson Wizard

#58
Quote from: tzachs on Sun 23/09/2018 22:25:35
Quote from: Crimson Wizard on Sun 23/09/2018 13:29:00
I think in the end it may be worth to not rely on engine's defaults and have a set of utility functions that init objects of different kinds. For example in the racing game I've referenced above I had function that setup a racing car object and it sets pivot to (0.5, 0.5), because it lets you to trivially apply rotation around sprite center.
This makes sense for a racing game, but I wouldn't want the average user to have to create utility functions for a point&click game. The engine should be geared with defaults that apply to the most common use-cases for point&click (or in the future, like we discussed, we might have templates with different defaults, geared to that specific template use case).

You may find different use cases for the Origin (I will use this term, which is equivalent to Pivot at the moment) within same game or even within same room. For example:
Origin with Y = 0 will be convenient when setting up objects that need to be on "floor", because you want to align their feet/bottom to particular place there.
Origin in center (0.5, 0.5) is convenient for top-down views, but may be also useful if you are making small flying objects like projectiles, visual effects etc.
Origin with Y = 1 might be useful if you align things in top to bottom order on screen programmatically (e.g. GUIs). And also if you have something walking on the ceiling, I guess :).
I think in general that whenever you are aligning objects programmatically alot in your game you may want to make sure they have convenient origins for that.

Otherwise you'd had to do extra maths in all non-default situations. AGS does not have custom origin, so I had to do additional calculations to position car "characters" in my racing game, converting from physics coordinates into AGS character coordinates. In MonoAGS I simply set the pivot.

Crimson Wizard

Since I was referencing examples of my work I decided to update my games to the latest version of MonoAGS too. Some issues were easy to fix, but I am currently trying to figure out what happened to the IImageRenderer interface which I implemented as SpriteFontRenderer in my game.

tzachs

Quote from: Crimson Wizard on Mon 24/09/2018 21:23:22
Since I was referencing examples of my work I decided to update my games to the latest version of MonoAGS too. Some issues were easy to fix, but I am currently trying to figure out what happened to the IImageRenderer interface which I implemented as SpriteFontRenderer in my game.
The IImageRenderer interface was removed and replaced with IRenderer and IRenderInstruction, see this PR: https://github.com/tzachshabtay/MonoAGS/pull/279
This was a follow-up to our discussion on refactoring the rendering logic: https://github.com/tzachshabtay/MonoAGS/issues/207

For the most simple example of this in action, see the pivot renderer component (basically just draws a cross where the pivot point is): https://github.com/tzachshabtay/MonoAGS/blob/master/Source/Engine/AGS.Engine/Graphics/Drawing/PivotRendererComponent.cs

Quote from: Crimson Wizard on Mon 24/09/2018 11:54:42
You may find different use cases for the Origin (I will use this term, which is equivalent to Pivot at the moment) within same game or even within same room.
Sure, I'm not disputing that, I'm just not sure we understood each other's point. What I argued against is your quote about "not relying on engine defaults and creating utility functions". I think that if you build a standard point & click game with this engine, then most chances are that the defaults match what you need (and if that's not the case, we should change that). If you're creating a different genre game, or have fancy non standard puzzles/sequences in your game, then yes, you might want to create utility functions for those (or, perhaps, we might have in the future, a feature similar to Unity's prefabs, that you can use instead of utility factories), but I don't think that applies to the vast majority of the games that will be created with this engine, and I don't think it applies to cat's game which is a standard point&click (mechanics wise, ignoring the wonderful puppets ;-D) with standard puzzles.

cat

Quote from: tzachs on Sun 23/09/2018 22:25:35
Quote from: cat on Thu 20/09/2018 21:36:31
When I use this and click somewhere, nothing happens in the game but I see an exception in the output window:
Exception thrown: 'System.IndexOutOfRangeException' in AGS.Engine.dll
I can't reproduce this, so I need more information.
Is this the entire error message, no additional stack trace? Also, where did you put this code? And can you also write the code you used to add the walkable area? Or alternatively, can you send me your project again (with the error reproducing)?

I put the init code in the loadUi function in the GameStarter.

I turned on "break on exception" and get this:
Spoiler

>   AGS.Engine.dll!AGS.Engine.StaticGrid.GetNodeAt(int iX, int iY) Line 128   C#
   AGS.Engine.dll!AGS.Engine.JumpPointParam.JumpPointParam(AGS.Engine.BaseGrid iGrid, AGS.Engine.GridPos iStartPos, AGS.Engine.GridPos iEndPos, bool iAllowEndNodeUnWalkable, bool iCrossCorner, bool iCrossAdjacentPoint, AGS.Engine.HeuristicMode iMode) Line 56   C#
   AGS.Engine.dll!AGS.Engine.EPPathFinder.getWalkPoints(AGS.Engine.BaseGrid grid, AGS.API.Position from, AGS.API.Position to) Line 45   C#
   AGS.Engine.dll!AGS.Engine.EPPathFinder.GetWalkPoints(AGS.API.Position from, AGS.API.Position to) Line 40   C#
   AGS.Engine.dll!AGS.Engine.AGSWalkComponent.getWalkPoints(AGS.API.Position destination) Line 337   C#
   AGS.Engine.dll!AGS.Engine.AGSWalkComponent.walkAsync(AGS.Engine.AGSWalkComponent.WalkInstruction currentWalk, AGS.API.Position location, bool straightLine, System.Collections.Generic.List<AGS.API.IObject> debugRenderers) Line 275   C#
   AGS.Engine.dll!AGS.Engine.AGSWalkComponent.processWalkInstruction(AGS.Engine.AGSWalkComponent.WalkInstruction previousWalk, AGS.Engine.AGSWalkComponent.WalkInstruction currentWalk) Line 262   C#
   AGS.Engine.dll!AGS.Engine.AGSWalkComponent.getWalkInstruction() Line 211   C#
   AGS.Engine.dll!AGS.Engine.AGSWalkComponent.onRepeatedlyExecute() Line 126   C#
   AGS.Engine.dll!AGS.Engine.AGSEvent<AGS.API.IRepeatedlyExecuteEventArgs>.InvokeAsync(AGS.API.IRepeatedlyExecuteEventArgs args) Line 64   C#
   AGS.Engine.dll!AGS.Engine.AGSGame.onUpdateFrame(object sender, AGS.Engine.FrameEventArgs e) Line 189   C#
   AGS.Engine.dll!AGS.Engine.AGSUpdateThread.RaiseUpdateFrame(double elapsed, ref double timestamp) Line 219   C#
   AGS.Engine.dll!AGS.Engine.AGSUpdateThread.DispatchUpdateFrame(object sender, System.EventArgs e) Line 185   C#
   AGS.Engine.dll!AGS.Engine.AGSUpdateThread.threadLoop() Line 167   C#
[close]

Quote
Not necessarily better, but you can get an empty bitmap as I said before, and then add a translate component which gives you the ability to "move" an area on the screen.
I guess I'll just stick to my implementation for now.

Quote
Ah, right, I think I changed it to all images after realizing the same logic applies, you can have multiple animations with different widths and it makes it easier to align by default (and also it's more consistent). I don't necessarily have too strong opinions on this, willing to discuss if you have a better suggestion.
Quote
I think cat's struggle here stems for the fact that she's porting an existing game, I don't think she'd encounter this issue when creating a new game (we'll see when people actually create new games, though).
I think the main issue with the pivot is (apart from being a bit obscure in the first place), that you don't set it to a specific pixel position but to a relative point within the sprite. This does not matter when positioning an object manually in an editor, but it is confusing when doing it in code. Especially with low-res games, where every pixel counts, this can be problematic, because you don't know the exact pixel position you have to use when pivot is 0.5.

Quote
The engine should be geared with defaults that apply to the most common use-cases for point&click (or in the future, like we discussed, we might have templates with different defaults, geared to that specific template use case).
This sounds like a good idea. I thought about suggesting something already.

Quote from: tzachs on Mon 24/09/2018 22:05:54
I don't think it applies to cat's game which is a standard point&click (mechanics wise, ignoring the wonderful puppets ;-D) with standard puzzles.
:-*

tzachs

From the stacktrace it seems that the issue is related to the way you created the walkable area, can you write the code you used for that (or send me the current project)? Thanks.

cat

#63
A quick update for everyone following here:
tzach fixed the issue and a few others I've found. They are already merged to the master branch.

And again I have a question:
The default position of text shown for character.SayAsync is a bit strange: it is a bit above (OK) but on the right side next to the character. Why?
I know I could write a custom ISayLocationProvider, but ideally, this basic thing should work right out of the box.



Edit: One more question: What does the ? do in
Code: ags
_bottle?.GetComponent<IHotspotComponent>().Interactions.OnInteract(AGSInteractions.INTERACT).SubscribeToAsync(onBottleInteract);

tzachs

Quote from: cat on Fri 05/10/2018 19:49:51
The default position of text shown for character.SayAsync is a bit strange: it is a bit above (OK) but on the right side next to the character. Why?
Because for some reason I thought the text should go to the right of the character (I was imagining a speech bubble).
Sorry, will fix soon.

In the meantime, you can set a TextOffset for your characters with a negative x to move the text to the left (you can also set a bigger label size so the text in the screenshot won't wrap, the default label width is 250 but your game is high resolution so makes sense to have a bigger label -> we should probably make the default label size resolution dependent).

For example:
Code: csharp

cornelius.SpeechConfig.TextOffset = (-100,0);
cornelius.SpeechConfig.LabelSize = (500,500);

cat

Yes, I think having the size resolution dependent makes sense.

About the other question I sneaked in (the ? in _bottle?.GetComponent...) - it's hard to google a question mark :P

tzachs

Quote from: cat on Fri 05/10/2018 19:49:51
What does the ? do in
Code: ags
_bottle?.GetComponent<IHotspotComponent>().Interactions.OnInteract(AGSInteractions.INTERACT).SubscribeToAsync(onBottleInteract);

The "?." is called the null conditional operator (sometimes nicknamed "the Elvis operator"). If "_bottle" is null it will skip the subscription afterwards.
Here's some more details on this operator: http://www.informit.com/articles/article.aspx?p=2421572

cat


Monsieur OUXX

usually when I'm searching weird operators I just type the name of the character in full letters into Google, followed by the name of the language, and there's always some stackoverflow question that deals with it. All I have to do then it to spot the official name of that operator somewhere in the discussion (here : "conditional operator") and then feed it again in google.

Example :
Code: ags

what is "C#" "question mark" operator
 

cat

Good idea, I'll keep that in mind 👍

cat

I found some time to work on the game again today and already found another bug :P

I click somewhere to walk there (walk-to-point). While the character is still walking, I right-click somewhere and the character says something async. The following happens:

  • The character switches to the speak animation but still moves towards the walk-to-point. Since there is no 'speakandwalk' outfit, I guess the character should stop walking?
  • When the say async is done, the character will quickly slide to the walk-to-point without any animation. This happens no matter if I click to stop the sayasync or not.

tzachs

Ah, you're right, the character should stop walking. Even if there was a 'speak and walk' animation, you'd still expect that when you right click, it will cancel the existing walk. A 'speak and walk' animation might be interesting to do, though, when you programmatically say something while walking.

As for the second bug, I think I understand why it's happening, after the say async is completed it doesn't switch back to the walk animation, and because you have "MovementLinkedToAnimation" with no moving animation, it defaults to the animation speed which is really fast (because it's configured to work in tandem with the animation, not as a "regular" walking speed).

Thanks, I'll look into fixing both bugs (and the talk position issue, I haven't forgotten it) soon.

cat

Quote from: tzachs on Thu 01/11/2018 15:41:02
Thanks, I'll look into fixing both bugs (and the talk position issue, I haven't forgotten it) soon.

Thanks! No need to hurry, there is still a lot to do on my side anyway.

cat

A question about including resources:

The default value for build action when I add a new resource is Content. I noticed that you use Embedded resource in the demo game. What is the difference between those actions and why did you pick embedded resource?

ChamberOfFear

Quote from: cat on Fri 09/11/2018 20:03:20
A question about including resources:

The default value for build action when I add a new resource is Content. I noticed that you use Embedded resource in the demo game. What is the difference between those actions and why did you pick embedded resource?

Embedded resource means that the file will quite literally will be embedded into the .exe that is built by the .NET compiler. If you go into the build directory of your game you will notice that the file can't be found, but the .exe is a little bigger in size.

Content will add the file into the build directory unaltered.

Choosing one over the other is a preference, doesn't really matter. I guess if you want to hide your assets from the end consumer then embedded resource makes sense, however anyone who is really interested in seeing assets will be able to if they know how.

tzachs

Right, though besides the preference choice (do I want to easily allow people to view/edit individual assets in the game?), I would say that embedding make some stuff easier:
1. If you don't embed then you need to worry about shipping your asset files with the game (and making sure that they're in the same folder hierarchy).
2. If you want to debug your game on mobile devices and you don't embed your assets, then it won't copy them to the device and you will need to copy those yourself.

And not embedding, otoh, makes it easier to swap asset files when developing (and we might add support for hot reloading those assets directly in the file explorer while your game is running -> though it might be possible for embedded assets as well, not sure).

For the reasons mentioned above, the default behavior that I'm currently thinking of for the editor is to have it as contents while developing, and embedded when deploying.
Though once we have a complete editor, we might be able to eliminate those problems with some programming on the editor side, so we'll see.

cat

Good to know, I'll stick to embedded for now. Thanks, guys!

cat

In my game there is no inventory, but when you pick up things, the mouse cursor will change to it - it will basically be your active inventory item.

So, when initializing the game, I create the inventory items and assign them to the player character. Later, OnInteract with a hotspot, I set the active item:
Code: ags

_character.Interactions.OnInteract(AGSInteractions.INTERACT).Subscribe(_ =>
{
    _game.State.Player.Inventory.ActiveItem = InventoryItems.Mouse;
});


However, this does not work with the TwoClickInputScheme. I will rewrite it, but the question is: What is the best approach? I could

  • Add a method for setting the active inventory to the TwoClickInputSchemeclass (public void SetActiveInventoryItem(IInventoryItem inventoryItem)). This is pretty simply but has two disadvantages: I have to remember to call it and I will somehow have to have a reference to the instance.
  • The next idea is to subscribe to repeatedly_execute in the TwoClickInputScheme and continuously check for the active item. This would be the AGS-style approach, but it feels unnecessary to do the check every cycle when the active item only changes ever so often. Which brings me to my third idea:
  • Is it somehow possible to attach an observer to ActiveItem? Then the InputScheme could just subscribe to it and do the cursor handling accordingly. The AGSInventory.Items property is already a BindingList, is there something similar for basic properties?

Any ideas/opinions?

tzachs

Hi, there's a new branch with a bunch of fixes for all the latest issues you mentioned: https://github.com/tzachshabtay/MonoAGS/tree/CatFixes2.
If you can test that everything works as expected I'll merge it to master.

Quote
However, this does not work with the TwoClickInputScheme. I will rewrite it, but the question is: What is the best approach? I could

  • Add a method for setting the active inventory to the TwoClickInputSchemeclass (public void SetActiveInventoryItem(IInventoryItem inventoryItem)). This is pretty simply but has two disadvantages: I have to remember to call it and I will somehow have to have a reference to the instance.
  • The next idea is to subscribe to repeatedly_execute in the TwoClickInputScheme and continuously check for the active item. This would be the AGS-style approach, but it feels unnecessary to do the check every cycle when the active item only changes ever so often. Which brings me to my third idea:
  • Is it somehow possible to attach an observer to ActiveItem? Then the InputScheme could just subscribe to it and do the cursor handling accordingly. The AGSInventory.Items property is already a BindingList, is there something similar for basic properties?
Great feedback!
What I did is a combination of 1 and 3.
The two buttons input scheme now has a SetInventoryCursor method, which you can either pass an inventory item or pass nothing which will make it take the current active item for the cursor.
In addition, I added the inventory item change event which I missed somehow.
So, you can now do:
Code: csharp
cornelius.Inventory.OnPropertyChange(nameof(IInventory.ActiveItem), () => scheme.SetInventoryCursor());


Quote
The character switches to the speak animation but still moves towards the walk-to-point. Since there is no 'speakandwalk' outfit, I guess the character should stop walking?
When the say async is done, the character will quickly slide to the walk-to-point without any animation. This happens no matter if I click to stop the sayasync or not.
So 2 fixes here:
1. Any user interaction will now trigger stop walking before applying the interaction.
2. I "added" a 'speak and walk' optional animation to the outfit.  If there's a programmatic SayAsync while the character is walking and you have a 'speak and walk' animation it will be used. If you don't have a 'speak and walk' animation then it will stop walking before speaking.
If you're using portraits, for example, and the walking animation itself can be used as 'speak and walk', then you can just write:
Code: csharp
cornelius.Outfit[AGSOutfit.SpeakAndWalk] = cornelius.Outfit[AGSOutfit.Walk];


Quote
the default label width is 250 but your game is high resolution so makes sense to have a bigger label -> we should probably make the default label size resolution dependent).
The default label size for speech is now resolution dependent (by default it's 80% of your virtual resolution).

Quote
The default position of text shown for character.SayAsync is a bit strange: it is a bit above (OK) but on the right side next to the character. Why?
Fixed, it's now centered above the character.

cat

Wow, that was quick 8-0

Quote from: tzachs on Sun 11/11/2018 03:27:19
The two buttons input scheme now has a SetInventoryCursor method, which you can either pass an inventory item or pass nothing which will make it take the current active item for the cursor.
In addition, I added the inventory item change event which I missed somehow.
Works, but only kind of:
When the player performs a right click when an inventory is active, there is a null reference exception at AGS.Engine.TwoButtonsInputScheme.SetInventoryCursor(IInventoryItem inventoryItem) in line 37
onRightMouseDown sets the ActiveItem to null, which then triggers the OnPropertyChanged handler.
I'd change SetInventoryCursor to reset the default cursor when null is passed and the active inventory is null. This would help regarding the exception, but also allow the programmer to change back the active inventory programmatically. This is needed when I use the active inventory item on a hotspot and this results in me losing the inventory item.

Another issue: You might want to check if the previous cursor should really be changed. Otherwise, there will be weird issues when setting two active items after each other without returning to the default cursor in between.
Consider renaming _previousCursor to _defaultCursor, then it will be more clear. You could also provide a method SetDefaultCursor. This way you can safely change the default cursor, no matter if there is currently an inventory active or not.

Quote
1. Any user interaction will now trigger stop walking before applying the interaction.
Confirmed (nod) I didn't test the SpeakAndWalk animation, though.

Quote
The default label size for speech is now resolution dependent (by default it's 80% of your virtual resolution).
Works, but 80% seems a bit too much. Maybe 70% is enough? (But this might be because of my setup with the stage frame around it.)
Btw, is it on purpose that the text wraps left-aligned instead of centered? It doesn't look that nice, especially when there is only a word or two in the second line.

Quote
Fixed, it's now centered above the character.
Confirmed (nod)

tzachs

Quote from: cat on Sun 11/11/2018 19:22:12
When the player performs a right click when an inventory is active, there is a null reference exception at AGS.Engine.TwoButtonsInputScheme.SetInventoryCursor(IInventoryItem inventoryItem) in line 37
onRightMouseDown sets the ActiveItem to null, which then triggers the OnPropertyChanged handler.
I'd change SetInventoryCursor to reset the default cursor when null is passed and the active inventory is null. This would help regarding the exception, but also allow the programmer to change back the active inventory programmatically. This is needed when I use the active inventory item on a hotspot and this results in me losing the inventory item.
Should be fixed now (still same branch).

Quote from: cat on Sun 11/11/2018 19:22:12
Another issue: You might want to check if the previous cursor should really be changed. Otherwise, there will be weird issues when setting two active items after each other without returning to the default cursor in between.
Consider renaming _previousCursor to _defaultCursor, then it will be more clear. You could also provide a method SetDefaultCursor. This way you can safely change the default cursor, no matter if there is currently an inventory active or not.
Right, I was doing copypasta from the rotating cursors scheme... :-[
Changed it now (there's a default cursor you can pass in the constructor, and also a DefaultCursor property you can set on the scheme afterwards).

Quote from: cat on Sun 11/11/2018 19:22:12
Works, but 80% seems a bit too much. Maybe 70% is enough? (But this might be because of my setup with the stage frame around it.)
Ok, I don't have any preference myself, so I changed it to 70%.

Quote from: cat on Sun 11/11/2018 19:22:12
Btw, is it on purpose that the text wraps left-aligned instead of centered? It doesn't look that nice, especially when there is only a word or two in the second line.
Not on purpose, but a bit more complicated to get right for all text rendering scenarios, I'm working on it and will update.

cat

It works except for one thing: I set the default cursor via the constructor, but it is only applied after changing to the inventory cursor and back. Of course I could set the cursor myself, but actually I'd expect this to happen automatically when TwoButtonsInputScheme.Start() is called. The same applies for the setter of the DefaultCursor property.

tzachs

Quote from: cat on Mon 12/11/2018 19:54:21
It works except for one thing: I set the default cursor via the constructor, but it is only applied after changing to the inventory cursor and back. Of course I could set the cursor myself, but actually I'd expect this to happen automatically when TwoButtonsInputScheme.Start() is called. The same applies for the setter of the DefaultCursor property.
Done.

Quote from: cat on Sun 11/11/2018 19:22:12
Btw, is it on purpose that the text wraps left-aligned instead of centered? It doesn't look that nice, especially when there is only a word or two in the second line.
I'm still not finished with this, but I pushed just enough so it should work for you as you expect, I hope.

cat

It works nicely! The cursor is now correct and the text centred nicely. Thanks :)

cat

#84
Next topic: following a character. I now realize that Cornelius Cat uses quite a lot of different features for such a short game :)

In the original AGS game I use the setting
Code: ags
cMouse.FollowCharacter(cCat, 150, 0);

This results in the mouse somewhat following the cat, but mostly running around, even when the cat is standing. How do I have to set the values in AGSFollowSettings to achieve the same?

Edit: Btw, there seems to be a problem when un-following a character with Follow(null):
System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=AGS.Engine
  StackTrace:
   at AGS.Engine.FollowTag.AddTag(IObject target, IEntity follower) line 23

tzachs

Quote from: cat on Wed 14/11/2018 20:26:24
Next topic: following a character. I now realize that Cornelius Cat uses quite a lot of different features for such a short game :)

In the original AGS game I use the setting
Code: ags
cMouse.FollowCharacter(cCat, 150, 0);

This results in the mouse somewhat following the cat, but mostly running around, even when the cat is standing. How do I have to set the values in AGSFollowSettings to achieve the same?
So looking at the manual, the first parameter is the distance the follower will stand from its target, and 150 means "about" 150 pixels. I don't know what "about" means exactly, haven't looked at the code, but let's assume that it's give-or-take 10 pixels.
In MonoAGS there are four parameters to control the distance: MinXOffset, MaxXOffset, MinYOffset and MaxYOffset. This gives you better control over the entire square you want to allow the follower. So to kind of mimic what AGS does (assuming that "about" is 10 pixels), I would give something like MinXOffset = 140, MaxXOffset = 160, MinYOffset = 140, MaxYOffset = 160. It's not going to be exactly the same, as AGS gives you one value so I'm guessing it forms a circle, where in MonoAGS it's a square. But this shouldn't make that much of a difference in practice, I think.

As for the second parameter, the eagerness. From looking in the manual it says that setting it to 0 will make the follower to always be on the move and that it will wander around the target like an energetic dog. It doesn't sound like that is what it actually does, from what you're describing, though.
Anyway, the first part, how often it moves, is controlled from MonoAGS by MinWaitBetweenWalks and MaxWaitBetweenWalks. To make it always on the move, set both to 0. The second part per your description, that it will be mostly running around and not actually following the cat, is something that you can control with WanderOffPercentage. If for example you set it to 50, there's a 50% chance for each walk to be to a completely random place, as opposed to the configured square. So for mostly running around to unrelated places, I guess you can set it to 80?
Let me know if it doesn't work out, maybe we need more parameters for better control.

Btw, I plan at some point to add presets to the follow settings with defaults to match common scenarios, so you'll be able to write something like:
Code: csharp

mouse.Follow(cornelius, AGSFollowSettings.Companion());

//It will also allow you to override specific settings, for example:
mouse.Follow(cornelius, AGSFollowSettings.Companion(wanderOffPercentage: 0));


Quote from: cat on Wed 14/11/2018 20:26:24
Edit: Btw, there seems to be a problem when un-following a character with Follow(null):
System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=AGS.Engine
  StackTrace:
   at AGS.Engine.FollowTag.AddTag(IObject target, IEntity follower) line 23

Should be fixed now (in your branch).

cat

#86
There still seems to be an issue with stopping the follow.
After fade in, I do
Characters.Mortimer.Follow(Characters.Cornelius);

Later, when using the mouse item on the gate, I have the following cutscene (not finished yet):
Code: ags

_game.State.Cutscene.Start();

Characters.Cornelius.Inventory.ActiveItem = null;
await Characters.Cornelius.WalkAsync((2479f, 0f));
await Characters.Cornelius.SayAsync("Mortimer, come here!");
Characters.Mortimer.Follow(null);
await Characters.Mortimer.WalkAsync((2035, 0f));
Characters.Mortimer.FaceDirection(Characters.Cornelius);
await Characters.Cornelius.SayAsync("I need you to help me with the gate.");
await Characters.Cornelius.SayAsync("You are small. Try to squeeze through it to the other side and open it for me.");
await Characters.Mortimer.SayAsync("* squeak *");

// TODO follow cat again

_game.State.Cutscene.End();

However, when it comes to the line Characters.Mortimer.Follow(null); the game freezes. I cannot even move the mouse cursor anymore.


On a different note: I tried the setting
Characters.Mortimer.Follow(Characters.Cornelius, new AGSFollowSettings(true, 80, 0, 10, 140, 160, 0, 10));
This works almost as desired, but the character will wander off till the end of the walkable area (much farer away than 160 pixel), is it possible to limit this somehow?

tzachs

Quote from: cat on Thu 15/11/2018 19:05:29
However, when it comes to the line Characters.Mortimer.Follow(null); the game freezes. I cannot even move the mouse cursor anymore.
Should be fixed now, and also:
1. Follow(null) followed by walk is problematic, as when you stop following the stop walking action will only be called on the next tick after you fired your walk, so it will stop your walk.
So I added a StopFollowingAsync to be used instead, so await it before walking.
2. I removed the blocking versions of FaceDirection because I'm concerned about their safety, so also replace it with await FaceDirectionAsync.

Quote from: cat on Thu 15/11/2018 19:05:29
On a different note: I tried the setting
Characters.Mortimer.Follow(Characters.Cornelius, new AGSFollowSettings(true, 80, 0, 10, 140, 160, 0, 10));
This works almost as desired, but the character will wander off till the end of the walkable area (much farer away than 160 pixel), is it possible to limit this somehow?
Ah, so if you want to stay in the vicinity of the cat, then you don't want to wander off at all, so I'll put it at 0. Wandering off is basically going wherever in the room without care for where the target is.
And btw, you can use named parameters which will make the constructor more readable:
Code: csharp

new AGSFollowSettings(wanderOffPercentage: 0, 
	minWaitBetweenWalks: 0, maxWaitBetweenWalks: 10, 
	minXOffset: 140, maxXOffset: 160, 
	minYOffset: 0);


I also added more parameters for tuning the follow behaviour (I don't think you care too much about those parameters for your scenario, but just in case):
1. Be able to have more control over the "wander off" if you want, so it's MinXOffsetForWanderOff etc, same as MinXOffset only just for the wander off phase (if you do want it).
2. Stay put percentage- if the follower is already in valid range of the target, what's the probability to keep moving to a new point anyway? It sounds like you want to always be on the move, so you can leave this at 0 which is the default.
3. Minimum walking distance- I noticed that it looks a little funny when the characters are moving just a few pixels, so this is to enforce a minimum distance for consecutive movements. This will be 10 pixels by default.
4. Stay on the same side percentage (one for x and one for y)- i.e if the follower is on the right of the target, what's the probability for it to stay to the right of the target on the next walk. This is 70% by default, so leaning a bit towards staying in the same side.

cat

Thanks! I changed the values and it looks quite good already. I might fiddle with it more a bit later, when I have the final walking speed etc.
Code: ags
Characters.Mortimer.Follow(Characters.Cornelius, new AGSFollowSettings(wanderOffPercentage: 0,
minWaitBetweenWalks: 0, maxWaitBetweenWalks: 100,
minXOffset: 10, maxXOffset: 260,
minYOffset: 0, maxYOffset: 10));


However, stopping still freezes the application (during await Characters.Mortimer.StopFollowingAsync();)

This is what my code looks like now:
Code: ags

_game.State.Cutscene.Start();

Characters.Cornelius.Inventory.ActiveItem = null;
await Characters.Cornelius.WalkAsync((2479f, 0f));
await Characters.Cornelius.SayAsync("Mortimer, come here!");
await Characters.Mortimer.StopFollowingAsync();
await Characters.Mortimer.WalkAsync((2035, 0f));
await Characters.Mortimer.FaceDirectionAsync(Characters.Cornelius);
await Characters.Cornelius.SayAsync("I need you to help me with the gate.");
await Characters.Cornelius.SayAsync("You are small. Try to squeeze through it to the other side and open it for me.");
await Characters.Mortimer.SayAsync("* squeak *");

// TODO follow cat again

_game.State.Cutscene.End();

When I click the pause button in VS while the game is stuck, it seems to be stuck in AGSGameWindow line 74 with call stack (not very helpful, I guess)
Spoiler

>   AGS.Engine.Desktop.dll!AGS.Engine.Desktop.AGSGameWindow.SwapBuffers() Line 74   C#
   AGS.Engine.dll!AGS.Engine.AGSGame.onRenderFrame(object sender, AGS.Engine.FrameEventArgs e) Line 225   C#
   AGS.Engine.Desktop.dll!AGS.Engine.Desktop.AGSGameWindow.onRenderFrame(object sender, OpenTK.FrameEventArgs args) Line 87   C#
   [External Code]   
   AGS.Engine.Desktop.dll!AGS.Engine.Desktop.AGSGameWindow.Run(double updateRate) Line 73   C#
   AGS.Engine.dll!AGS.Engine.AGSGame.Start() Line 150   C#
   CorneliusCat.Desktop.exe!CorneliusCat.GameStarter.Run() Line 19   C#
   CorneliusCat.Desktop.exe!CorneliusCat.Desktop.Program.Main(string[] args) Line 10   C#
[close]

Another question: if follow(null) can cause problems, wouldn't it be better to remove this synchronous call completely?

Crimson Wizard

Quote from: cat on Sat 17/11/2018 16:18:14
When I click the pause button in VS while the game is stuck, it seems to be stuck in AGSGameWindow line 74 with call stack (not very helpful, I guess)

Not following all the story, but sometimes you may try switching to other threads in "Debug->Windows->Threads" and see if any of available thread stacks has any visible relation to the recent command you are stuck at.

cat

Good idea. Sadly, I didn't find anything that seems more helpful.

tzachs

Ok, I think I really fixed it this time, can you try again?
Sorry.. :-[

Quote from: cat on Sat 17/11/2018 16:18:14
Another question: if follow(null) can cause problems, wouldn't it be better to remove this synchronous call completely?
Yeah, I'm not really sure what's the best way to handle it yet.
Here's the thing, the follow method just sets the follow target, and then on each game tick it manages its "state machine" to see what's the next move to perform (and if it's stop following, then it will want to stop the existing walk).
So it kind-of follows the same behavior of ChangeRoom in AGS where the script you write after ChangeRoom will actually happen before the room is changed (not a problem in MonoAGS, btw, that's why it's ChangeRoomAsync). And StopFollowingAsync basically calls Follow(null) with an additional task that you can hang onto to wait until the StopWalking happens in the next tick.

Now, I can't really disallow Follow(null) in compile-time yet (though it will be possible when c# 8 is released). I can make it throw an exception in run-time, though the only problem with Follow(null) is the scenario in which you follow(null) and then walk immediately after it (it might ignore that walk) -> in all other scenarios it will work fine, so I'm not too keen on throwing an exception if you pass null to it.
So here's the possible directions I thought I can take this (let me know what you think):
1. I can make Follow into FollowAsync (and remove StopFollowingAsync) and always return a task, even though it's completely not needed if you don't pass null. And also, it might be confusing for the user, as making it async hints that it will await the entire follow until done following, which is not correct. So I'm not in favor of this approach.
2. I can keep things the way they are for now, and when c# 8 is released to disallow nulls to Follow in compile time.
3. I can change it so it won't stop the current walk when you stop following with Follow(null). I'm not sure what AGS does in that scenario, actually (does anybody know?). But I could just let the character complete its last walk as a follower and then just not do any more walks. And I can make StopFollowingAsync to call Follow(null) and then call StopWalkingAsync, so if you do want to stop walking you'll call StopFollowingAsync and if you don't care you can call Follow(null).
4. I can add an option for StopWalking to accept a specific walking task. Then in the follow component I'll call stop walking on "my" specific walk, so there can't be any confusion, the follow component will never stop a walk issued by somebody else (and I can delete StopFollowingAsync completely). I'm not sure how complicated this is to pull off, but this option might be useful in other future scenarios as well.

cat

It works now :)

Ah, I am so used to Javascript with Closure compiler type annotations, I completely forgot it is not possible to have non-nullable types in C#.

I prefer option 3. I tried it in AGS and there the current walk is not stopped. The mouse will continue walking until it reaches the cat and then stop. StopFollowingAsync sounds like a good add-on. I think it's much easier to explain this way.

tzachs

Ok, done in your branch, so you can choose if you want Follow(null) or StopFollowingAsync depending if you want to stop the current walk or not.

cat

Great! I stick to StopFollowingAsync, since I want to walk directly afterwards.

cat

Today I started to work again on the port.

Until now, I was only using the walk-left animation. When I added the walk-right animation, it looks weird. I see the correct sprites (i.e. flipped) but the starting point is off. It is hard to explain, but it almost looks as if the mouse is rotated around its nose. It looks like it is not flipped inside the sprite bounds, but mirrored along the left border, i.e. the whole character seems to jump.

Here is how I init the ani:
Code: ags

            var walkAni = game.Resolver.Resolve<IAnimation>();
            walkAni.Frames.Add(new AGSAnimationFrame(walkSprite1));
            walkAni.Frames.Add(new AGSAnimationFrame(walkSprite2));
            var walkRightAni = walkAni.Clone();
            walkRightAni.FlipHorizontally();
            var walk = new AGSDirectionalAnimation { Left = walkAni, Right = walkRightAni };

            var outfit = new AGSOutfit();
            outfit[AGSOutfit.Idle] = idle;
            outfit[AGSOutfit.Walk] = walk;
            outfit[AGSOutfit.Speak] = speak;

tzachs

You need to center your sprites (they're using lower-left by default):
Code: csharp

walkSprite1.Pivot = (0.5f, 0f);
walkSprite2.Pivot = (0.5f, 0f);


Note that if you load an outfit or directional animation from the factory, it does it for you automatically, i.e if you only supply the left animation it will flip and center it to create the right animation (or vice-versa).

cat

Now I am a bit confused  ???

Previously I had problems putting an object in the right place. There it turned out that the pivot was initially at 0.5 and I had to reset it to 0.0 (https://www.adventuregamestudio.co.uk/forums/index.php?topic=56055.msg636595291#msg636595291 ff). But now the pivot suddenly is at 0.0 and I have to set it to 0.5? And why does flipping the animation also flip the pivot?

How do I load the outfit/animation from the factory? Here is the full init code:
Spoiler
Code: c#

        public async Task<ICharacter> LoadAsync(IGame game)
        {
            _game = game;

            AGSLoadImageConfig loadConfig = new AGSLoadImageConfig();

            var factory = game.Factory.Graphics;

            var walkSprite1 = await factory.LoadSpriteAsync(_baseFolder + "mouse_walk1.png");
            var walkSprite2 = await factory.LoadSpriteAsync(_baseFolder + "mouse_walk2.png");
            var speakSprite = await factory.LoadSpriteAsync(_baseFolder + "mouse_speak.png");

            var idleAni = game.Resolver.Resolve<IAnimation>();
            idleAni.Frames.Add(new AGSAnimationFrame(walkSprite1));
            var idle = new AGSDirectionalAnimation { Left = idleAni };

            var walkAni = game.Resolver.Resolve<IAnimation>();
            walkAni.Frames.Add(new AGSAnimationFrame(walkSprite1));
            walkAni.Frames.Add(new AGSAnimationFrame(walkSprite2));
            var walkRightAni = walkAni.Clone();
            walkRightAni.FlipHorizontally();
            var walk = new AGSDirectionalAnimation { Left = walkAni, Right = walkRightAni };

            var speakAni = game.Resolver.Resolve<IAnimation>();
            speakAni.Frames.Add(new AGSAnimationFrame(walkSprite1));
            speakAni.Frames.Add(new AGSAnimationFrame(speakSprite));
            var speak = new AGSDirectionalAnimation { Left = speakAni };

            var outfit = new AGSOutfit();
            outfit[AGSOutfit.Idle] = idle;
            outfit[AGSOutfit.Walk] = walk;
            outfit[AGSOutfit.Speak] = speak;

            _character = game.Factory.Object.GetCharacter("Mortimer", outfit).Remember(game, character =>
            {
                _character = character;
                subscribeEvents();
            });

            _character.SpeechConfig.TextConfig = AGSTextConfig.ChangeColor(_character.SpeechConfig.TextConfig, Colors.Gray, Colors.Black, 1f);
            var approach = _character.AddComponent<IApproachComponent>();
            approach.ApproachStyle.ApproachWhenVerb["Talk"] = ApproachHotspots.WalkIfHaveWalkPoint;

            _character.DisplayName = "Mortimer Mouse";
            _character.IsPixelPerfect = true;
            _character.StartAnimation(_character.Outfit[AGSOutfit.Idle].Left);

            return _character;
        }
[close]

tzachs

Quote from: cat on Sun 27/01/2019 19:09:15
And why does flipping the animation also flip the pivot?
It doesn't. The problem is that the FlipHorizontally function, instead of flipping the texture, it flips the scale on the horizontal axis.
Scaling is done from the pivot point. So a -1 scale (on x) when the pivot is 0 will shift the entire sprite to the other side.

There is an item in the backlog for changing this behavior and just flipping the texture, which should make flipping work regardless of the sprite's pivot.

For your previous issue, I believe you changed the character's pivot point. Note that you can change the pivot on the character but also on each individual animation frame (sprite) as well. So not sure, but it might work having the character's x pivot be 0 (for your previous issue: keeping the same co-ordinates as AGS) and your sprite's x pivot to be 0.5 (for flipping to work in the current scheme).
If it doesn't work, let me know, and I'll work on the texture flip item now.

Quote from: cat on Sun 27/01/2019 19:09:15
How do I load the outfit/animation from the factory?
The factory methods assume that each animation loop is in its own folder.
So for example, you can setup your files in the following structure:
mortimer/
    -> walk/
         -> left/
             -> mouse_walk1.png, mouse_walk2.png
    -> idle/
         -> down/
             -> mouse_walk1.png
    -> speak/
         -> down/
             -> mouse_speak.png

And then you can load the entire outfit with:
Code: csharp

var baseFolder = "mortimer";
var outfit = await game.Factory.Outfit.LoadOutfitFromFoldersAsync(baseFolder, walkLeftFolder: "walk/left",
				idleDownFolder: "idle/down", speakDownFolder: "speak/down");


Or alternatively you can load directional animations with "game.Factory.Graphics.LoadDirectionalAnimationFromFoldersAsync" and construct your outfit from the directional animations like you did in your code.

cat

Okay, setting the pivot of each sprite to 0.5 did it.

I reuse some sprites for various animations and making separate folders for it would be unnecessary overhead. I'll stick to manual sprite creation for now.

tzachs

Quote from: cat on Mon 28/01/2019 20:35:27
Okay, setting the pivot of each sprite to 0.5 did it.
Great to hear!

Quote from: cat on Mon 28/01/2019 20:35:27
I reuse some sprites for various animations and making separate folders for it would be unnecessary overhead. I'll stick to manual sprite creation for now.
Sure.
Hopefully when the editor is ready it will allow you to share the sprites without having to work so hard for it.

cat

Quote from: tzachs on Tue 29/01/2019 02:22:19
Hopefully when the editor is ready it will allow you to share the sprites without having to work so hard for it.
For me it is not a question of working hard to set up the folder structure, but of "don't repeat yourself". If I have the same sprite in various folders and want to change it, I have to take care to replace every instance of it. Also, with high-res games, this could increase size (granted, not with my three mouse sprites, but you get the idea).

tzachs

Quote from: cat on Tue 29/01/2019 09:00:38
Quote from: tzachs on Tue 29/01/2019 02:22:19
Hopefully when the editor is ready it will allow you to share the sprites without having to work so hard for it.
For me it is not a question of working hard to set up the folder structure, but of "don't repeat yourself". If I have the same sprite in various folders and want to change it, I have to take care to replace every instance of it. Also, with high-res games, this could increase size (granted, not with my three mouse sprites, but you get the idea).
By "working hard" I meant writing all of that code that you wrote in order not to duplicate the sprites. The editor will generate that code for you.

cat


cat

#104
Finally some more MonoAGS!

I found a slight quirk (using the latest master branch, but could be the issue existed before):

I change to my main room using
Code: ags
await game.State.Player.ChangeRoomAsync(Rooms.Stage, 2300, 100);


In the room, OnAfterFadeIn, I start a cutscene
Code: ags

        private void OnAfterFadeIn()
        {
            PlayIntroCutscene();
        }

        private async void PlayIntroCutscene()
        {
            Characters.Cornelius.PlaceOnWalkableArea();
            
            await Characters.Cornelius.SayAsync("Hello, here I am.");
            await Characters.Mortimer.SayAsync("* squeak *");

            FollowCat();
        }

Now, when I click somewhere on the screen while the cat is talking, it will immediately slide to the clicked position without walk animation being played.


And another question: You probably have told me already, but how does setting an object's baseline work? I didn't find a baseline property.

tzachs

Quote from: cat on Sun 28/04/2019 20:25:55
Finally some more MonoAGS!
Hooray!  ;-D

Quote from: cat on Sun 28/04/2019 20:25:55Now, when I click somewhere on the screen while the cat is talking, it will immediately slide to the clicked position without walk animation being played.
Hmm, I'll look into that.
But, do you even want to allow walking the cat during the cutscene? Because if you don't, you can put "Characters.Cornelius.Enabled = false;" at the beginning of the scene (and "Characters.Cornelius.Enabled = true" when you want to return control to the player).

Also, slightly off-topic, but you can wrap your cutscene with "game.State.Cutscene.Start()" and "game.State.Cutscene.End()" in order to allow the player to skip the scene (how the player can trigger skipping the scene is controlled with "game.State.Custcene.SkipTrigger").

Quote from: cat on Sun 28/04/2019 20:25:55
And another question: You probably have told me already, but how does setting an object's baseline work? I didn't find a baseline property.
The short answer is that the Z property is the object's baseline (and by default, unless you explicitly set Z, Z will equal Y).

The longer answer is that the order in which objects render first depends on their render layer. For example, by default, all GUIs are created in a separate layer than room objects, and as the GUIs layer has a lower Z than the object layer, all guis will appear in front of all room objects, even if their individual Z says otherwise.
In other words, the object's Z is compared only for objects in the same render layer.
Also, you can add a Z offset for individual sprites, those are added to the object's Z when the comparison is made. So if you have an animation for an object in which you need one of the frames to appear behind something, you can give that specific frame an individual Z offset to make that happen.
And lastly, if you use object composition (i.e Cornelius the cat might have a gun as a separate object and that gun has the cat as its parent), then the children objects will only be compared against themselves, and will use their parents for comparing against other objects.

cat

#106
Quote from: tzachs on Sun 28/04/2019 22:42:35
Hmm, I'll look into that.
Thanks!

Quote
But, do you even want to allow walking the cat during the cutscene? Because if you don't, you can put "Characters.Cornelius.Enabled = false;" at the beginning of the scene (and "Characters.Cornelius.Enabled = true" when you want to return control to the player).
Does this mean I have to disable the character everytime I have a cutscene? With AGS, talking was blocking, so noone could walk around during a scripted dialog scene.
Didn't you build something that disables walking while talking?

Edit: I found the post I was talking about: https://www.adventuregamestudio.co.uk/forums/index.php?topic=56055.msg636597241#msg636597241

Quote
Also, slightly off-topic, but you can wrap your cutscene with "game.State.Cutscene.Start()" and "game.State.Cutscene.End()" in order to allow the player to skip the scene (how the player can trigger skipping the scene is controlled with "game.State.Custcene.SkipTrigger").
Thanks, but this is not the final intro cutscene anyway. I wanted to write it last to avoid having to skip a cutscene everytime when testing.

Quote
The short answer is that the Z property is the object's baseline (and by default, unless you explicitly set Z, Z will equal Y).

The longer answer is that the order in which objects render first depends on their render layer. For example, by default, all GUIs are created in a separate layer than room objects, and as the GUIs layer has a lower Z than the object layer, all guis will appear in front of all room objects, even if their individual Z says otherwise.
In other words, the object's Z is compared only for objects in the same render layer.
Also, you can add a Z offset for individual sprites, those are added to the object's Z when the comparison is made. So if you have an animation for an object in which you need one of the frames to appear behind something, you can give that specific frame an individual Z offset to make that happen.
And lastly, if you use object composition (i.e Cornelius the cat might have a gun as a separate object and that gun has the cat as its parent), then the children objects will only be compared against themselves, and will use their parents for comparing against other objects.
Wow, lot's of options! Thanks for the explanation.

tzachs

Quote from: cat on Mon 29/04/2019 08:49:19
Does this mean I have to disable the character everytime I have a cutscene? With AGS, talking was blocking, so noone could walk around during a scripted dialog scene.
Right, I didn't want to disable the player automatically for every event handling, because I wanted to allow writing "responsive cutscenes" (which is not always trivial to do with AGS).
The plan is for the editor to (by default) automatically inject disabling/enabling the player when handling events (along with starting/ending a cutscene and hiding/changing the mouse cursor).

Quote from: cat on Mon 29/04/2019 08:49:19
Didn't you build something that disables walking while talking?

Edit: I found the post I was talking about: https://www.adventuregamestudio.co.uk/forums/index.php?topic=56055.msg636597241#msg636597241
Right, hence the "i'll look into that"  :)

Crimson Wizard

#108
Quote from: tzachs on Mon 29/04/2019 14:44:20
Quote from: cat on Mon 29/04/2019 08:49:19
Does this mean I have to disable the character everytime I have a cutscene? With AGS, talking was blocking, so noone could walk around during a scripted dialog scene.
Right, I didn't want to disable the player automatically for every event handling, because I wanted to allow writing "responsive cutscenes" (which is not always trivial to do with AGS).
The plan is for the editor to (by default) automatically inject disabling/enabling the player when handling events (along with starting/ending a cutscene and hiding/changing the mouse cursor).

tzachs, I had some thinking about this recently, after discussing blocking/nonblocking problem with a person, and there's a concept of "control stack" shaping in my head.
To put things briefly, you logically divide the game entities into "control layers" (which may be unrelated to anything else), which form a "control stack". Then, you may order certain layer to "pause" which would efficiently stop updates in the layer and anything below.

Such scheme will let user to decide which parts of the game are paused at any given time. For example, on the top of the stack there's a main menu. Lower are in-game menus (inventory etc). If game menus have animations, calling main menu in "modal mode" will automatically stop them.
If you want blocking dialogs, you can move participants to the separate layer above the rest of the game, which would effectively allow to pause everything "behind them".
Want blocking cutscene - have "cutscene layer" and attach involved entities to it.
If you like game keep playing idle animations but want all the story-related triggers to pause, you split story script and run it attached to some "controlling entity" on the lowest layer. And so on.

tzachs

Yes, and I think it can maybe go hand-in-hand with the "time machine" feature we once discussed: i.e a group of entities are assigned to a specific "time machine" (your "control layer") which then allows you to slow down/speed up time, rewind, forward and pause, whereas pause is the same pause that you were talking about (pausing time will also effectively block inputs).

cat

That concept of "time-machine" and "control-layers" sounds very interesting!

cat

I created the first very basic dialog. It works, but the dialog options are partly hidden behind my stage GUI. Is is possible to change the order of elements? Or do I have to implement IDialogLayout as written in the documentation?

tzachs

Ah, good catch.
I've put the dialogs on the same layer as the GUIs by default, so they can be behind other GUIs. I'll change the default and put them in their own layer.

Meanwhile, you can change the layer for your stage to be behind other GUIs (but still in front of room objects):
Code: csharp

stage.RenderLayer = new AGSRenderLayer(AGSLayers.UI.Z + 1);

cat

Quote from: tzachs on Tue 30/04/2019 21:14:21
Ah, good catch.
I've put the dialogs on the same layer as the GUIs by default, so they can be behind other GUIs. I'll change the default and put them in their own layer.

Meanwhile, you can change the layer for your stage to be behind other GUIs (but still in front of room objects):
Code: csharp

stage.RenderLayer = new AGSRenderLayer(AGSLayers.UI.Z + 1);

Nice! I didn't know you could create your own layers.
I agree that it could be useful to put other GUIs in front of the dialog. Would it be possible to change the Z-order of the dialog itself but keeping it in the UI Layer?

One more question: In the start-up logic of the game, I set game.Settings.Defaults.SpeechFont and TextFont to a custom font. This works very well for spoken text as well as the "Loading" label of AGSSplashScreen, but it doesn't work for the dialog options. They are tiny (remember, the game is high-res) and with a different font. Would it be possible to use the TextFont there?

tzachs

Quote from: cat on Wed 01/05/2019 12:24:49
Would it be possible to change the Z-order of the dialog itself but keeping it in the UI Layer?
Yes, this should do the trick:
Code: csharp

var dialog = factory.GetDialog(...);
dialog.Graphics.Z = 50;


Quote from: cat on Wed 01/05/2019 12:24:49
One more question: In the start-up logic of the game, I set game.Settings.Defaults.SpeechFont and TextFont to a custom font. This works very well for spoken text as well as the "Loading" label of AGSSplashScreen, but it doesn't work for the dialog options. They are tiny (remember, the game is high-res) and with a different font. Would it be possible to use the TextFont there?
Ah, right, I missed that.
It should be fixed in this branch: https://github.com/tzachshabtay/MonoAGS/pull/316
Do you want to test it before I merge to master?

cat

Quote from: tzachs on Thu 02/05/2019 03:36:26
Do you want to test it before I merge to master?
Sure, I will test it in the evening.


I had a quick look at the pull request and a more general question comes up:
It is great that you can specify lots of config for each individual dialog line, but I'd assume that most users will want to have all dialog options look the same, i.e. have one color for all options and another color for all hovered. In your pull request you create a custom getDialogOption() function, but ideally something like this should be provided by the framework.
Is it possible to set the color as default? Or have a hook where to put a custom function for styling all dialog options, like in AGS where you can use a few functions to make your owner drawn GUI? Not sure what would be the best option for this. And would it be possible to use graphics for dialog options?
What are your plans on how this will develop in the future?

tzachs

Right. Ideally I would like you to be able to set defaults for everything, i.e the default settings in which you set the default text & speech fonts should also have a section for dialogs where you can set default colors, default render layer, etc (maybe I'll just add it for this PR, it's pretty simple).

As for hooks, you can either, like you said, override IDialogLayout and make changes to dialog options there, or you can override IDialogFactory and create your own dialog & dialog options.

And yes, it's possible to use graphics for dialog options:
Code: csharp

var option = factory.GetDialogOption("Hello!");
option.Label.Image = myImage; 

//and label has all of the regular object's components, so you can also do:
option.Label.StartAnimation(myAnimation);
option.Label.TweenY(...); //etc

cat

Quote from: tzachs on Thu 02/05/2019 03:36:26
It should be fixed in this branch: https://github.com/tzachshabtay/MonoAGS/pull/316
I tried it and it works  :)

I noticed one more quirk with the Z-order: I set the z-order of the stage frame panel with
Code: ags
panel.RenderLayer = new AGSRenderLayer(AGSLayers.UI.Z + 1);


The dialog text is now shown above the stage frame, but the black background box is shown behind.

When I use
Code: ags
dialog.Graphics.Z = 50;

the same thing happens. Even weirder, in this case the black background is even shown behind the characters!

tzachs

Right, another thing to fix (forgot to put the black box on the same layer as the dialog options).
I'll add the fix to the PR, but in the meantime, you can do:

Code: csharp

var dialog = factory.GetDialog(...);
dialog.Graphics.RenderLayer = AGSLayers.UI;

tzachs

I pushed the fixes to the PR.

One breaking change: defaults.TextFont and defaults.SpeechFont changed to defaults.Fonts.Text and defaults.Fonts.Speech.
I also added defaults.Fonts.Dialog (which you can leave blank to use the text font), and also a default section for dialogs (defaults.Dialog) in which you can set stuff like colors, for example to change the color of hovered dialog options to green:

Code: csharp

game.Settings.Defaults.Dialog.Hovered.Brush = game.Factory.Graphics.Brushes.LoadSolidBrush(Colors.Green);


I also added a dialog rendering layer which by default sits in front of the UI and behind the speech (and you can override it in the defaults.Dialog section as well).

cat

Good news first: The Z-order works and also setting the color. However, suddenly the idle dialog options were shown right aligned:

[imgzoom]https://www.clowdergames.com/stuff/MonoAGS/dialog_alignment.jpg[/imgzoom]

Highlighted and chosen lines are shown just fine. I only set the default for the idle dialog options:

Code: ags
                var defaultFont = game.Factory.Fonts.LoadFontFromPath("Fonts/Acme-Regular.ttf", 20f, FontStyle.Regular);
                game.Settings.Defaults.Fonts.Speech = defaultFont;
                game.Settings.Defaults.Fonts.Text = defaultFont; 
                game.Settings.Defaults.Dialog.Idle = new AGSTextConfig
                {
                    Brush = game.Factory.Graphics.Brushes.LoadSolidBrush(Colors.OrangeRed),
                    Font = defaultFont
                };


I then realized that I probably don't even have to set a new Object, just setting the brush does the trick and everything is left-aligned again:
Code: ags
                var defaultFont = game.Factory.Fonts.LoadFontFromPath("Fonts/Acme-Regular.ttf", 20f, FontStyle.Regular);
                game.Settings.Defaults.Fonts.Speech = defaultFont;
                game.Settings.Defaults.Fonts.Text = defaultFont;
                game.Settings.Defaults.Dialog.Idle.Brush = game.Factory.Graphics.Brushes.LoadSolidBrush(Colors.OrangeRed);


So, doing it this way it works alright. I just wonder why a new config object would yield right-aligned text...


I hope all my bug reports are not too frustrating  (roll)

tzachs

Quote from: cat on Sat 04/05/2019 14:39:14
I just wonder why a new config object would yield right-aligned text...
That's because I've set the defaults for the text config creation in the factory method (factory.Fonts.GetTextConfig) and not in the object itself. I'm leaning into eventually providing factory methods for generating everything instead of using constructors for the object directly, for several reasons:
1. Discoverability- you'll be able to browse the factories, and just with the help of code completion and nothing else you'll be able to see everything you can create.
2. Consistency- every time you need to create something, you'll know that you need to find a factory for it.
3. Context- The object might need information that will not be "easy" for the user to pass if using the constructor. For example, we might add a general default text config object like I have just done for the dialogs, in that case the AGSTextConfig constructor will need access to that default object -> the factory method will have that context and do it for you so you won't have to figure it out for yourself...
4. Injection- by using the factory you'll be able to inject your own factory (or a plugin author's factory) with different behaviors than those built in in the engine.

Anyway, for now I pushed a fix to have defaults for AGSTextConfig to match the defaults for the factory method, but not sure if in the future we'll want to continue exposing those constructors.

Quote from: cat on Sat 04/05/2019 14:39:14
I hope all my bug reports are not too frustrating  (roll)
Not at all, I live for this! (just realized that sounds sad).

cat

Thank you for the detailed explanation. I will make sure to use the factory whenever possible.

Quote from: tzachs on Sat 04/05/2019 22:24:59
Anyway, for now I pushed a fix to have defaults for AGSTextConfig to match the defaults for the factory method, but not sure if in the future we'll want to continue exposing those constructors.
How would hide the constructors? Make them internal?
How does the factory pattern work anyway in connection with autofac dependency injection? When do you use what?

Quote
Quote from: cat on Sat 04/05/2019 14:39:14
I hope all my bug reports are not too frustrating  (roll)
Not at all, I live for this! (just realized that sounds sad).
Then I will continue posting them  :)

cat

#123
Good news - I was able to code the dialog tree for the puzzle :)

The next step will be adding actions. The expected type is System.Action. But I see that you just pass a function there - how is this supposed to work and what can I do? Is a function the same as an Action?
Can I just add one function and code all the stuff I want to write there? Or should I add one action per statement?

Edit: Right, I could actually read the documentation myself  (roll)
Action is just a function delegate. https://docs.microsoft.com/en-us/dotnet/api/system.action?view=netframework-4.8
However, the question above is still valid: what is best practise for dialogs?

tzachs

Quote from: cat on Sun 05/05/2019 14:32:03
How would hide the constructors? Make them internal?
Yes.

Quote from: cat on Sun 05/05/2019 14:32:03
How does the factory pattern work anyway in connection with autofac dependency injection? When do you use what?
The factory holds the resolver inside of it, and uses it to resolve dependencies (example).
As a user of the engine, you should be using factories to create stuff, and only use the resolver directly when you want to inject stuff (i.e if you find yourself in the need to use the resolver to resolve something, it might be a sign of a missing factory).

Quote from: cat on Sun 05/05/2019 17:41:34
Good news - I was able to code the dialog tree for the puzzle :)
;-D

Quote from: cat on Sun 05/05/2019 17:41:34
However, the question above is still valid: what is best practise for dialogs?
Hmm, one reason to break things up is for debugging: each dialog action can be disabled separately (dialogAction.Enabled = false), so, for example, you can do a single action with all of the talking and another action for giving an inventory item, and then you can disable the talking when you're testing stuff.
Other than that, I added single actions mostly planning for the editor, but no reason to use them if you're using the engine directly (and for the editor I'll probably change how it's written now anyway, I want to combine the current dialog actions with general visual scripting when I get around to adding it to the editor, as it's basically the same thing).

tzachs

Quote from: cat on Sun 28/04/2019 20:25:55
Now, when I click somewhere on the screen while the cat is talking, it will immediately slide to the clicked position without walk animation being played.
I pushed a fix here, can you test to see that it solved the issue for you?
Thanks.

cat

Before pulling the branch, I tried it with the old version again and had problems reproducing it because I changed the intro scene in the meantime.

I narrowed the initial problem down to where I place the character initially. When I do

Code: ags
splashScreen.Events.OnAfterFadeIn.SubscribeToAsync(async () =>
            {
               ...
                game.State.Player = Characters.Cornelius;

                await game.State.Player.ChangeRoomAsync(Rooms.Stage, 2300,[b]100[/b]);


and in the room script
Code: ags
private async void PlayIntroCutscene()
        {
            Characters.Cornelius.PlaceOnWalkableArea();
            
            await Characters.Cornelius.SayAsync("Hello, here I am.");
            await Characters.Mortimer.SayAsync("* squeak *");

            FollowCat();
        }

the issue happens.

When I place the cat directly on the walkable area
Code: ags
await game.State.Player.ChangeRoomAsync(Rooms.Stage, 2300,[b]5[/b]);

it didn't happen.

Anyway, I can confirm that with the changes from your pull request, it doesn't happen in any case - it seems to be fixed  :)


Please note that I might work less on MonoAGS related stuff for the next weeks because I'm joining MAGS.

tzachs

The bug happened whenever you started the walk after the character started talking AND it was still walking when done talking. So maybe the walk you do when you place the character yourself was shorter and ended before you were done talking, otherwise I have no explanation.
Anyway, good that it's solved, I merged it to master (and I think that covers all of your reported issues, let me know if I forgot something).
And good luck with MAGS  :)

cat

Tiny update: I managed to add an action with AddAsyncActions and it seems to work  :)

cat

There is this line I copied from the demo game for character initialization:
Code: ags
_character = game.Factory.Object.GetCharacter("Cornelius", outfit).Remember(game, c => _character = c);


What is the purpose of .Remember? Do I have to do this everytime I change the outfit? Or how would I go about changing the outfit?

tzachs

The purpose of "Remember" was for the save system, and I don't know if it will be kept in the same way after I'll rewrite it.
The main reason for this is that you're creating the character into your own class variable, i.e it's not part of the state, so we don't have a way to load it automatically ("Remember" is just you passing a function to the engine that's called after loading a game to allow you to assign it to your variable again).
Anyway, it's only intended for the initial object creation (i.e this code will only usually be in the editor generated code, you won't need to touch it at all in your own code), you don't need it for changing an outfit, you just need to do:

Code: csharp

_character.Outfit = myOutfit;


cat

Setting the outfit does not immediately update the idle view. Only when the character walks a bit or speaks the new frames are used.

tzachs

Hmm, you can start the idle animation yourself after setting the outfit with something like this:

Code: csharp

_character.StartAnimation(_character.Outfit[AGSOutfit.Idle].Down);


Or instead, I think just calling face direction should work as well.

I can make it automatic, but then what if somebody doesn't want to start the idle animation in the new outfit? What if they want to continue the same animation from the previous outfit? But if we go down that route, then what do we do if there is no equivalent animation in the new outfit to match the old outfit?
As a more powerful example, I can imagine a scenario of a "falling from the skies Clark Kent" who wants to switch to "flying Superman": now, I do have a "falling" animation for superman but I don't want to switch to it, I want to switch to "flying" in the new outfit -> the engine doesn't have a way to know that, so I think having it as a two step process (i.e first select the outfit, then select the animation) is cleaner.
But I'm open to suggestions on how to make this more friendly.

cat

It seems illogic that the idle animation does not start automatically after setting the outfit but it starts after any other animation was started. IIRC the current AGS implementation requires you to initialize the idle animation separately and this often confuses authors. If an outfit does have an idle animation, I think it should work out of the box. If I don't want to use an idle animation, I simply don't create one.

I didn't want to search the thread, but wasn't there a thing about the character not being visible initially until you started some animation manually? I think this is the same topic.

As a game author, I think this stuff should work right away. There are people with great coding skills making games, but don't forget about those authors that are mostly writers or painters and mostly noobs regarding code. Imagine all the help threads they would create "I changed the outfit but it doesn't show, what should I do?"

tzachs

I'm still not completely convinced (if you set an outfit while the character is walking, won't you expect the new outfit to also change to walking, and not idle, for example?), but as you represent 100% of the user-base you get what you want.  (nod)
I'll make the change soon.

cat

Quote from: tzachs on Sun 21/07/2019 02:57:56
if you set an outfit while the character is walking, won't you expect the new outfit to also change to walking, and not idle, for example?
Actually, that's exactly what I'd expect. I'd expect that the character uses the correct animation for the current state, i.e. idle->idle animation, walking->walking animation, climbing->climb animation (how would I go about climb animation anyway?) etc.
Not sure what about states that don't have an animation. AGS would probably pick the first frame of the walking animation and do not animate.

At the moment, when switching outfits, the last outfit remains visible unless an action triggers an animation change. I don't think this is how it should work.

Quoteas you represent 100% of the user-base you get what you want.  (nod)
(laugh)

tzachs

Quote from: cat on Sun 21/07/2019 19:41:50
(how would I go about climb animation anyway?)

An outfit is just a map from a string key to a directional animation, and you can add any animation you want, so you can do for example:
Code: csharp

var clarkKentClimb = _game.Factory.Graphics.GetDirectionalAnimation(_baseFolder, "CK/Climb/Left", "CK/Climb/Right", "CK/Climb/Down", "CK/Climb/Up");
clarkKentOutfit["Climb"] = clarkKentClimb;

var supermanClimb = _game.Factory.Graphics.GetDirectionalAnimation(_baseFolder, "Superman/Climb/Left"); //no budget to animate all dirs, the left animation will always be used
supermanOutfit["Climb"] = supermanClimb;

//Then, when you want to animate:
_character.Outfit = supermanOutfit;
_character.StartAnimation(_character.Outfit["Climb"].Down);

...
_character.Outfit = clarkKentOutfit; //With the new addition, if superman was still climbing when changing the outfit, clark kent will be climbing now as the climb animation exists for it as well (and I guess if the climb animation doesn't exist, I'll rollback to the idle animation, and if the idle animation doesn't exist, I'll do nothing)


(But an animation doesn't have to be associated with an outfit, you can also start an animation directly).

SMF spam blocked by CleanTalk