User:Josh Oosterman/Design Study
Line 16: | Line 16: | ||
[[Image:Animator-screenshot.png]] | [[Image:Animator-screenshot.png]] | ||
− | In order to understand some of the classes I'll explain the method of interaction. A scene consists of several figures (such as a person, or snake), and | + | In order to understand some of the classes I'll explain the method of interaction. A scene consists of several figures (such as a person, or snake), and props (such as a soccer ball) which can be added using the menu on the right. The background and weather effects can be customised, using the menu also. Each figure has multiple 'handles', which can be clicked and dragged to manipulate the figure. The primary handle (red in the above screenshot) is used to move the figure around the scene. The other handles (blue in the above screenshot), are used to pose the figure. For example, dragging a red handle upwards would cause a figure to lift their arm. |
Often, animation systems support a full range of transformations such as translation, rotation, scaling etc. This system deliberately constrains the types of movement allowed. That is, at each joint in a figure, there is only a rotation property. Translation (i.e. separating limbs) and scaling (i.e. leg changing size) are not supported, as these are not sensible actions for stick figures to take. The figure's body model is designed to mimic a real person, and support joint angle limits, etc. | Often, animation systems support a full range of transformations such as translation, rotation, scaling etc. This system deliberately constrains the types of movement allowed. That is, at each joint in a figure, there is only a rotation property. Translation (i.e. separating limbs) and scaling (i.e. leg changing size) are not supported, as these are not sensible actions for stick figures to take. The figure's body model is designed to mimic a real person, and support joint angle limits, etc. |
Revision as of 01:39, 30 September 2010
Contents |
Introduction
Overview
I wanted to create a tool to animate scenes, and humanoid characters. Eventually I want be able to create complex animations which can be loaded into a 2d game. This would take too long for a 427 assessment, so I've simplified the problem to having an interactive stickmen tool. The extensibility of the design of the system is critical so I can extend it in the future.
The system was fully designed and implemented for COSC427, and was not based on previous work. Since everyone looked at my design study I have made the following changes:
- Better explanation of project & concept of Handles
- Minor OO improvements
- Updated UML and split it into parts
- Got animation nearly working. This is in the source, but is excluded from my design study for size.
- More thorough discussion of design forces & maxims
A screenshot of the current version is shown below.
In order to understand some of the classes I'll explain the method of interaction. A scene consists of several figures (such as a person, or snake), and props (such as a soccer ball) which can be added using the menu on the right. The background and weather effects can be customised, using the menu also. Each figure has multiple 'handles', which can be clicked and dragged to manipulate the figure. The primary handle (red in the above screenshot) is used to move the figure around the scene. The other handles (blue in the above screenshot), are used to pose the figure. For example, dragging a red handle upwards would cause a figure to lift their arm.
Often, animation systems support a full range of transformations such as translation, rotation, scaling etc. This system deliberately constrains the types of movement allowed. That is, at each joint in a figure, there is only a rotation property. Translation (i.e. separating limbs) and scaling (i.e. leg changing size) are not supported, as these are not sensible actions for stick figures to take. The figure's body model is designed to mimic a real person, and support joint angle limits, etc.
System Goals
Within my 427 Project
- Customisable backgrounds
- Posable figures (Such as Stickmen)
- Props
- Extensible - so I can add the below features eventually
Outside the scope of this project, but to be added in the future:
- Interpolation between Keyframes
- Advanced Inverse Kinematics & Constraints on Joints
- Export angles & positions as animation file to be used in games
System UML Class Diagram
The image below is a simplified UML class diagram of nearly all of the classes in the system.
Explanation
The above UML class diagram looks large, but the system is very simple and it should be easy to find your way around.
The far right classes MainForm and Program are for the .NET generated GUI for the application. This is not an important part of the OO design and is mostly ignored in the design study. Similarly, the Point and Vector classes next to it are geometry utility classes I have written, which will also be ignored in this study.
At the top left is the IDrawable interface. This defines just one method, but is used throughout the design. The Draw method takes a reference to a Graphics context.
Scene
At the top left we can see the Scene class. The scene class is just that - a scene in an animation. The application lets you use only one scene object, but it is not a singleton as it is feasible multiple scenes could be supported in the future. The Scene class consists of a background, and a collection of AnimatedObjects. The Background of a scene could be a SimpleBackground, optionally decorated with effect using the LightningBackground or RainBackground classes. RainDrop is a lightweight class used by the RainBackground decorator. The background classes can be seen in the top center of the diagram. The ordering of AnimatedObjects in a Scene is not important, but .NET 2.0 does not include a Set class so a List<AnimatedObject> was used instead.
Animated Objects
The inheritance tree of AnimatedObject constitutes the left third of the UML diagram. The two subclasses of the abstract AnimatedObject are Prop and Figure. A Prop represents a single solid object, such as a soccer ball (as seen in the screenshot above). Props have only a Sprite and a position. The Figure subclass is more complex, and is used to represent posable composite objects such as humans or animals. Figure is further explained below. AnimatedObject also uses a Handle object, accessible through its MainHandle property. The syntax "+ MainHandle { get; }" on the UML diagram represents a .NET property, equivalent to a getter method.
A Handle is used to position or pose an AnimatedObject. Handles are rendered as red or blue squares, as seen in the screenshot above. The MainHandle handle is used to position the AnimatedObject within the scene. The Figure class has a further collection of handles, which can be used to move arms and legs, for example. The Handle class defines a delegate type and event called MoveCallback that is called when a handle is moved. The AnimatedObject can subscribe to this event and react when the handle is moved.
Although it is not implemented at the moment, eventually the AnimatedObjects would store several instances of their state, one for each key frame in the animation. A state could be calculated for each frame in the animation by interpolating between these key frames. When this is implemented, the state of an AnimatedObject would be stored as the set of positions of it's handles. The memento pattern would probably be used to do this.
Figures
Now we look at the Figure class. The subclasses Man and Snake do not need much explanation. They currently only define a constructor, which constructs the Limbs and Joints in the body. The Factory pattern was not used here, as it is feasible these subclasses may have other special behaviours. The Figure class does have a strategy reference to an instance of LimbAnimationStrategy, which is used to calculate limb positions and angles from a handle position. This follows the strategy pattern. NaiveLimbStrategy and InverseKinematicStrategy are the current options. For an explanation of Inverse Kinemeatics, see Oliver's design study.
The Figure has a root BodyPart. The BodyPart class is abstract and has two concrete implementations. An Extremity is one of these, which has a Sprite, and is used for the hands, feet and heads of figures. A Limb is more complex as it can have children (for example, when modelling the human body the lower arm could be seen as a child of the upper arm, and the hand (an extremity) can be seen as a child of the lower arm). A Limb has a length, and a Pen which are used to render it (in a stick figure fashion). Importantly, it also has a collection of connector Joint objects. The Joint objects are used to define the children of a Limb. The Joint class has a To property, which is the child BodyPart, and an Angle property, which is the angle at that joint (between the parent and child body parts).
Other
Finally, the Sprite class can be seen near the top right of the diagram. A Sprite is a static image that can be used in the animation. For example, the Extremities or Backgrounds. Internally, it just wraps a .NET Bitmap object, but the interface does not depend on this. Sprites can be created by either loading an image file, or creating an empty image of a specified size and colour. The benefit of the Sprite class is to provide resource sharing, using the flyweight design pattern.
Style & Encapsulation
Not only the OO design aspects were considered in the implementation. I have used C# coding conventions in the implementation, which are indirectly important as they help the communication and understanding of the design. Class and method names start with an upper case letter, variables start with a lower case letter, and member variables (fields) all have the prefix "m_".
The code is barely commented at all. This is not because I am lazy - It is because I genuinely believe there is not a lot that needs commenting. As long as the design is understood, there should be no nasty surprises at the code level.
Although I'm a slow convert to the idea, Object Encapsulation has been used throughout the design. Public fields are avoided altogether, and are instead wrapped by public Properties (getters and setters) if required. Fields are marked as protected so that the subclasses can get at them.
Design Patterns
Strategy
The strategy pattern was used for limb animation. This is because I may want to add new behaviors in the future, once I actually understand Inverse Kinematics...
- Context = Figure
- Strategy = LimbAnimationStrategy
- ConcreteStrategy = NaiveLimbStrategy
- ConcreteStrategy = InverseKinematicStrategy
Decorator
The Background classes participate in the decorator pattern. This is because that most of the kind of behaviors you might want in a background should be stackable, and I can't anticipate them all up front.
- Component = Background
- ConcreteComponent = SimpleBackground
- Decorator = BackgroundDecorator
- ConcreteDecorator = RainBackground, LightningBackground
Observer
The observer pattern was kind of used with the Handle class. This is so that the figures and objects can adjust themself as the handles are moved. This was actually implemented using C# events, which is a language supported Observer system.
- Subject = Handle
- Observer = Figure, Prop
Flyweight
A very lightweight version of flyweight was used with the Sprite class so that it could avoid loading many instances of the same image into memory. Sprite has a static method to load a Sprite, which checks its pool and returns a mutable reference to the shared object.
- Flyweight, FlyweightFactory = Sprite.
Interesting Maxims Followed
Big Design Up Front
I'm generally pretty opposed to this, but because this system was so small and simple I designed it almost entirely before I implemented it. This worked well for me.
Model the real world
This is a rule that I seem to have problem using generally, but in this case it worked well. Some concepts in the design (InverseKinematicsStrategy and Handle) do not have real world equivalents. The important concepts of a Scene, Prop and Figure all model their real world equivalents. I was initially having trouble designing the body modelling classes, but drew inspiration from the real world concepts of Limbs and Joints.
Acyclic Depedencies Principle
The packages in the design are acyclic. Stickman.GUI depends on Stickman.AnimatedObjects, Stickman.AnimatedObjects.Figures and Stickman, which in turn use the util package Stickman.GraphicUtil.
Avoid Downcasting, Avoid inheritance for Implementation, Command Query Separation
None of that nonsense here.
Encapsulate That Which Varies
Implementation details are hidden behind the interface, and object encapsulation is used.
No Concrete Base Classes
All base classes in the design (AnimatedObject, Figure, Background and BodyPart) are sensible abstractions which can not be directly instantiated.
Maxims Violated
Model View Controller
I decided not to strictly follow MVC in my design. For example, the BodyParts are responsible for both modelling the state of a body, and drawing themselves. I
Law of Demeter
The Law of Demeter has been broken in my design. While I think it is a stupid rule, it has brought to my attention a poor part of my design. The Joint class doesn't really have any behavior, instead, it acts as a connector which holds a reference to another BodyPart. This means we get ugly things like the MoveTo() method in Limb calling "j.To.MoveTo()", a long ugly chained call. Joint could be changed to have a MoveTo() method itself, but I'd like to think on this some more.
Data Class, Keep related data and Behaviour in one Place
As above, the Joint class is a bit silly. This should be fixed.
Code
The system was implemeted in C# and can be obtained here. To build and run is trivial -- Open Stickman.sln in Visual Studio and build. You'll need Windows and VS2008 or later.