Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SpriteRenderComponent #211

Merged
merged 1 commit into from
Jan 25, 2018

Conversation

ivan-mogilko
Copy link

@ivan-mogilko ivan-mogilko commented Jan 21, 2018

For #206.

This adds ISpriteRenderComponent interface and its AGSSpriteRenderComponent implementation.
SpriteRenderer is supposed to become a focal point of image-based objects (and eventually implement rendering itself), having its properties updated by others, such as AnimationComponent.
Moved Border and DebugDrawPivot properties from Animation to SpriteRender component.
Had to add OnNextFrame event to IAnimation to let AnimationComponent know when to update renderer.

NOTE 1:
When I was about to create this PR, the doubts came to me, and I thought that I might be doing something wrong.
Initially, I proposed this component as a medium between renderer and 2D image-based components, but since we decided there will be IRenderer interface, that components could may implement themselves (e.g. mentioned in #207), does that make SpriteComponent redundant? Because, to think of it, AnimationComponent and ImageComponent (especially latter one) can implement IRenderer too.
I am mostly wondering if ImageComponent and SpriteRenderComponent are essentially duplicating each other, or not.

NOTE 2:
Current implementation is a first try. There were few things I had doubts about in technical sense (added TODO comments for these).

First of all, template generation failed for me for some reason, introducing unexpected changes to generated classes. I will try to investigate again, but for now I just implemented the necessary changes by hand.

Secondly, since AnimationComponent is not rendered directly, but has to update SpriteRenderComp., I had to somehow let it know that animation frame changes, so added new event to IAnimation. The first tests in DemoGame show that rendering works, but there were couple of possible glitches, and I am not sure whether my code covered all possible cases when animation may be updated.

Copy link
Owner

@tzachshabtay tzachshabtay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Besides the comments I left on the code, also note the failing tests (you can see them in the Mac CI build link, here: https://travis-ci.org/tzachshabtay/MonoAGS/jobs/331353522)

/// <summary>
/// An animation container. This gives access to the animation, and allows to start a new animation
/// which will replace the old animation.
/// </summary>
[RequiredComponent(typeof(ISpriteRenderComponent))]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big deal, as we're not actually using the RequiredComponent yet, so just and FYI, placing the attribute on the interfaces was a mistake on my part. The component dependencies should be on the implementing class, not on the interface (for example, an alternative implementation of the animation component might not need the sprite component). Once I get to actually using it (in #185 ), I plan on moving all the attributes from the interfaces to the implementations.

/// Allows to subscribe to the animation frame change.
/// </summary>
// TODO: Should this be a blocking event?
IEvent OnNextFrame { get; }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to add this event: IAnimationState implements INotifyPropertyChanged, so you can use the PropertyChanged event instead, and check for the CurrentFrame property change:

animationState.PropertyChanged += onAnimationStatePropertyChanged;

private void onAnimationStatePropertyChanged(object sender, PropertyChangedEventArgs args)
{
   if (args.PropertyName != nameof(IAnimationState.CurrentFrame)) return;
   
   //... change sprite here ...
}

Note that you will not find the code that actually fires that event, that's because I'm using https://github.com/Fody/PropertyChanged for injecting the code on compile.

if (currentAnimation != null)
{
currentAnimation.State.OnNextFrame.Subscribe(OnAnimationFrame);
currentAnimation.State.OnAnimationCompleted.TrySetResult(new AnimationCompletedEventArgs(false));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a mistake: currentAnimation holds the currently playing animation, the one we're about to replace with the new animation. So you should not subscribe to currentAnimation here, you should unsubscribe currentAnimation and subscribe to animation -> this probably explains the glitches you've seen.


private void OnAnimationFrame()
{
// TODO: perhaps instead checking this every time, subscribe and unsubscribe
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not understand the todo comment: the on frame event (or rather, the property changed event as I mentioned above) is in animation state, part of the animation which is not a component: it's a parameter to the StartAnimation method, so there's no component add/remove for subscribing?

Copy link
Author

@ivan-mogilko ivan-mogilko Jan 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I meant here is that there possibly is a way to ensure this callback is only called if _spriteRender is not null (therefore proceed without conditional check):

  • do not subscribe to the event unless sprite component is present;
  • subscribe to the event when sprite component is added (if animation is already running);
  • unsubscribe from the event if sprite component gets removed.

But this depends ofcourse on whether component may be added/removed in the period of time between event firing and callback running (or not).

@tzachshabtay
Copy link
Owner

First contribution, I'm very excited :)

Regarding your comments here:

  1. See my review above regarding the event and the glitches.

  2. I don't think the sprite component is redundant, like you observed in our earlier discussion, the animation component should be just about handling the state, and not actually handling the visuals so we wouldn't want it to implement IRenderer itself.

  3. I've neglected the template generation lately as I've been working on Mac and there's a bug in the template generation in VS for Mac, so I've also been updating manually. Very possible that there are bugs there, if you want to investigate them, that's cool, otherwise I'll investigate when I finish my current work.

@tzachshabtay
Copy link
Owner

Cool, only thing missing is fixing the tests.

@ghost
Copy link
Author

ghost commented Jan 24, 2018

Following our discussion, latest commit introduces ISpriteProvider interface for SpriteRenderComponent to get active sprite from. Component itself does not have sprite setter anymore.
Currently only AnimationComponent implements ISpriteProvider (because ImageComponent works through animations too).

Something I wanted to ask, do you prefer that pull request commits to be squashed eventually (hiding code rewrite iterations), or want to keep them all?

@tzachshabtay
Copy link
Owner

I usually prefer to keep the commits, unless you have any objections. Ok, looks good and the tests pass. Good to merge?

@ghost
Copy link
Author

ghost commented Jan 25, 2018

I usually prefer to keep the commits, unless you have any objections.

Well, I used to recreate WIP branches in case when the final code changed too much along the way compared to initial idea, because that could cause problems understanding what has changed and why, especially when searching for an origin of a bug.

For that reason I'd rather clean it up first, it should take a little time.

Ok, looks good and the tests pass.

Test failure still a mistery to me, last time I checked there was something related to savegame serialization, which I have zero knowledge about. Somehow it does not happen now?

@tzachshabtay
Copy link
Owner

Well, I used to recreate WIP branches in case when the final code changed too much along the way compared to initial idea, because that could cause problems understanding what has changed and why, especially when searching for an origin of a bug.

Sure, but I mean, do you have any objections of keeping the commits for this PR? Or do you want me to squash? Or do you want to recreate?
I'm fine with whatever you prefer.

Test failure still a mistery to me, last time I checked there was something related to savegame serialization, which I have zero knowledge about. Somehow it does not happen now?

It looks fine now, and you did change stuff for serialization (you removed the sprite from the contract), so it was possibly related to that. I wouldn't worry too much about it, the serialization is not in a workable state now anyway and would be completely revamped soon(ish).

@ghost
Copy link
Author

ghost commented Jan 25, 2018

Sure, but I mean, do you have any objections of keeping the commits for this PR? Or do you want me to squash? Or do you want to recreate?

Yes, I would like to recreate it, if you don't mind, that should take 15 mins at most.

It looks fine now, and you did change stuff for serialization (you removed the sprite from the contract), so it was possibly related to that. I wouldn't worry too much about it, the serialization is not in a workable state now anyway and would be completely revamped soon(ish).

Actually, I noticed I forgot to remove a Sprite property from ContractSpriteRenderComponent, it just not used in serialization, but still declared.

@tzachshabtay
Copy link
Owner

Ok.

@ghost
Copy link
Author

ghost commented Jan 25, 2018

I have to ask, what fields need to be in this ProtoContract? There are fields in the component that are set freely, like Border and DebugDrawPivot, there is readonly field CurrentSprite, and also SpriteProvider.

@tzachshabtay
Copy link
Owner

The proto contract is everything that's saved as part of a saved game. CurrentSprite is taken from the provider, so no need to save it. The other 3 need to be saved.

This adds ISpriteRenderComponent interface and its AGSSpriteRenderComponent implementation. SpriteRenderer is supposed to become a rendering "contractor" for 2D sprite-based components, such as ImageComponent or AnimationComponent.
SpriteRenderComponent stores a reference to ISpriteProvider, from which it receives "current sprite" to draw.
AGSAnimationComponent implements ISpriteProvider.

Moved Border and DebugDrawPivot properties from Animation to SpriteRender component.
Made GLImageRenderer refer to SpriteRenderComponent for drawing (instead of AnimationComponent). It is assumed, that parts of GLImageRenderer rendering function will be merged into SpriteRenderComponent on later stages.
@ghost
Copy link
Author

ghost commented Jan 25, 2018

Well, I think I'm done.
Some builds failed, but logs say something about missing nuget package, so idk.

@tzachshabtay
Copy link
Owner

Yeah, the linux build is not currently working, you can ignore that. The tests pass, merging.

@tzachshabtay tzachshabtay merged commit 61ff4b2 into tzachshabtay:master Jan 25, 2018
@ivan-mogilko ivan-mogilko deleted the refact--spriterender branch January 27, 2018 22:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants