User:Benjamin Gibson/Design Study
Navigation shortcuts: Wiki users:Benjamin Gibson
Contents |
Problem overview
For my design study I have chosen to use a mobile graphics assignment (from cosc416). The goal of this assignment is to create a small pacman like game for mobile phones, using java ME. Java ME is a scaled down version of java, it works mostly like earlier versions of java (no generics etc).
I am designing code that will be able to be used to create very simple 2D games, I want a flexible code base that can be used to make several different types of games. In particular I want to be able to add new objects into the game easily (such as a new type of playable character, or add walls etc) and make it easy to change how the game is played (things like what does pushing the up button do).
For my design I have split my graphic assignments code into two parts, the Game Library and the Game Actual. The Game Library is the code base mentioned above and the Game Actual is an implementation of a PAC man game made on top of the Game Library for the purposes of my graphics assignment.
Note that my design is only about the Game Library, not about the Game Actual, while I have implemented the Game Actual it is not a part of my design study. The idea is I could make a completely different game just as easily from the same Game Library. I will provide the Game Actual as proof that the Game Library can be used to create games, though the code in the Game Actual was rushed to finish and should not be considered as part of my design. In addition any code in MyCanvas relating to the GameActual should not be considered part of the design.
Terms
- Collision detection: Collision detection is a term used in graphics applications when you are trying to find out if the graphical representation of two or more objects overlap, or "collide". An example of this would be checking if a players avatar is touching a wall, if it is then you would want to stop them from moving through the wall.
- Canvas: This is similar to JCanvas in swing, graphics are drawn on the canvas and then the canvas is drawn on the screen, for my purposes things won't get any more complex than this.
- Sprite: Sprites are animated images, much like .GIF files, these are used to represent most animations.
- Layer Manager: GameObjects are added to the Layer Manager(a part of the J2ME library) which then draws them on the screen. It also manages some other things of little relevance to my design study, such as the order in which objects are drawn on the screen.
Initial Design
UML Diagram
Note: some elements have been removed from the diagram as they relate to the Game Actual, not the Game library (such as empty part in the middle of MyCanvas's fields) ignore these parts.
Design Description
The above diagram shows my initial design for the Game Library
- Midlet is just a main class that is necessary for java ME, this class can pretty much just be ignored.
- MyCanvas is a canvas, as described above, this class is where the magic happens, it is created by Midlet and then runs the game, creating game objects and then constantly looping redrawing all the graphics and using the CollisionDetector to find collisions.
- CollisionDectector is a singleton class which maintains a list of all collidable objects, detect will check all collidable objects against each other for collisions and returns a list of collision objects, one for each collision.
- Collision represents the collision between two collidable objects, it simply takes the two objects that collided and stores them as fields, resolve calls resolveCollision on both collidables.
- GameObject is an interface that represents an object that actually exists in game (such as a player, barrel, wall etc). Collidable, Movable and InputListener are all types of GameObjects, a GameObject will most likely inherit from one or more of these types.
- Collidable is an abstract class that in its constructor adds itself to the CollisionDetector, this means that any object that extends Collidable will be added to the CollisionDectector upon creation. Collidable objects have an action method that is called when they collide with another object, this object is passed in as a parameter, in addition to this each collidable object has three boolean fields, lethal, killable and impassable, which are used to help the object understand how to react to the collidable object passed in as a parameter to action (for example if a killable object collides with a lethal object, the killable object should die).
- Movable is a simple interface for GameObjects that can move.
- InputListener is a simple interface for GameObjects that accept input from the user (key presses).
- Camera is, as the name suggests, in charge of everything the camera needs to do, such as follow objects as they move.
Design Critique
Having the three boolean fields of Collidable is not very flexible, if I ever wanted to make a new property for Collidable objects I would have to edit the code for all classes extending collidable.
Killable is an attribute that should not belong exclusively to collidable objects, all objects should be killable.
Input listener should not inherit from GameObject, someone may at some point wish to create an input listener not attached to a GameObject, such as one that counts key presses.
When a collision occurs Collidables have to know about each others fields in order to chose how to react to the collisions, while I have achived this with out using getters, I think this still violates Tell, Don't Ask
Design Patterns
- Singleton - The CollisionDetector is a singleton class with getTheCollisionDetector as the getInstance method. The CollisionDetector stores a list of all collidable objects and checks them for collisions when asked (using the detect method). I could have made this list inside MyCanvas like I did with the list of InputListener (MyCanvas keeps a list of all Input/listeners and informs them of user input), but I decided to keep this data in its own class to avoid cluttering MyCanvas and so collidable objects can easily add themselves to it.
Updates
- Attempted to refactored Collision detection system to conform to Tell, Don't Ask better, while I removed the getter methods I think that I did not remove the problem, see Design Critique.
- Restructured the program into several packages so that protected objects aren't available to the whole program.
Final Design
To address the issues discussed in the initial design I have significantly restructured my design, as presented below.
UML Diagram
Note: some elements have been removed from the diagram as they relate to the Game Actual, not the Game library (such as empty part in the middle of MyCanvas's fields) ignore these parts.
Design Description
The above diagram shows my final design for the Game Library.
- Midlet is just a main class that is necessary for java ME, this class can pretty much just be ignored.
- MyCanvas is the same as in the intial design, except that it now maintaints a list of GameObjects and in its main loop does the collision detection and sends them user input.
- Camera is, as the name suggests, in charge of everything the camera needs to do, such as follow objects as they move.
- Coordinates represents x and y coordinates on screen.
- GameObject is an interface that represents an object that actually exists in game (such as a player, barrel, wall etc). A GameObject has coordinates to keep track of where it is. A GameObject has methods for collision detection, death of the object, user input and movement.
- GameObjectDecorator and BasicGameObject are parts of the Decorator pattern implmented for GameObjects, this allows GameObjects to gain new behavior at run time.
- Solid contains behavior for objects that cannot be moved through, this takes the place of collidable objects with the boolean impassable set to true. In its resolve collision method it tells the object it collided with to undo its last move. This means that solid objects can only stop other objects from moving into them (by pushing them backwards), though it could be extended so that it works for solid objects that are moving as well. This class could have been placed in the Game Actual instead, but I left it here to help present some typical behavior.
- Lethal contains behavior for objects that kill other objects upon collision, this takes the place of collidable objects with the boolean lethal set to true as well as collidable objects with the boolean killable set to true. This class could have been placed in the Game Actual instead, but I left it here to help present some typical behavior.
Design Critique
GameObjects have a wide set of methods, most of which do not promise much. This is because not all GameObjects will use each of these methods, for example a wall will most likely not use the move method. I don't like this, it means that some objects are inheriting methods not relevant to them, however it is because of this I have been able to fix my original problem with collisions. In this way this solution is a trade off.
Note that the second CollidesWith method using Sprite is necessary because in Java ME collision detection is done using a method of the Sprite class.
MyCanvas violates One key abstraction, it is a Canvas onto which everything is drawn, but also handles the game loop, this behavior should probably be moved into it's own class (perhaps game loop?), however time does not allow.
Don't repeat yourself is violated with the collidesWith methods, these methods will normally be the same but aren't implemented in the decorator class so that some classes don't have be able to collide with other classes. This results in the same code being written in several places.
Design Patterns
- Decorator - as described in the design description I have used the Decorator pattern. GameObject is the Component, GameObjectDecorator is the Decorator, BasicGameObject is an example of a ConcreteComponent and Solid is an example of a ConcreteDecorator. In my code my implementation of Decorator pattern is incomplete, I have not finalized which methods will call their super classes versions and which ones won't.
Design Maxims
I have followed a large set of design maxims, however most of these are maxims I normally follow. For this reason I will categorize the maxims I have followed into two categories, less significant maxims and more significant maxims. The more significant maxims are as such because they are new to me and I have made a special effort in applying them to my design.
Less Significant Maxims Followed
Avoid downcasting, Avoid inheritance for implementation, Liskov substitution principle, Open closed principle, Dependency inversion principle, Interface segregation principle, Avoid god classes, Avoid equals, Avoid side effects, Keep it simple, Stable abstractions principle
More significant Maxims Followed
- Tell, Don't Ask - In order to finish my project in time I at one point neglected this maxim, however I have recently reworked my code to follow it (except for the Coordinate class which I only realized belonged in the Game Library instead of the Game Actual when it was already to late). however I ran into one instance where it I found it hard to conform to the rule cleanly. The instance was in the action method of the collidable class, the method took a collidable object as a parameter and then asked it about three different Booleans that classified it and decided how to act based off this. First I tried having another method called on collidable that then had the object pass itself and its three Booleans as parameters into the other collidables action method, this got rid of the need for getters for the Booleans at least. In the final version of my design I got rid of this problem all together by enriching the interface of the objects. Now when two objects collide they will won't try to find out anything about the other object and instead issue it commands. For example a Lethal object will call the other objects die method, some objects die methods will do nothing, such as a wall that cannot die.
- Encapsulation boundary - I subscribe to the idea of an object encapsulation boundary rather than a class encapsulation boundary. In class it was suggested that when using an object encapsulation boundary we should default to protected fields instead of private fields. I have done this in only one class, Collidable. This is because this is the only class that has fields that are intended to be inherited and I do not like that protected fields also grant access to classes within the same package. In java you used to be able to declare private protected fields which work like private fields but also allow subclasses access to the field, this would be my preference. Taking this approach does mean that if someone where to come along later and subclass one of my concrete classes then they would not be able to access the classes fields, I feel that this is a lesser evil than sharing all my classes fields amongst their packages since these classes are not intended to be subclassed.