Skip to content

Latest commit

 

History

History
110 lines (90 loc) · 32.8 KB

Design.md

File metadata and controls

110 lines (90 loc) · 32.8 KB

CS 308: VOOGASalad Plan Document

####Robert Duvall and the Global Variables Brandon Choi
Jeremy Fox
John Gilhuly
Cosette Goldstein
Kaighn Kevlin
Danny Oh
Nathan Prabhu
Tom Puglisi
Eric Saba
Sierra Smith
Nina Sun
Kei Yoshikoshi

#Introduction The problem we are trying to solve is to design a flexible Tower Defense graphical authoring environment and Engine. The Tower Defense genre is a subgenre of Real Time Strategy Games that is defined by users placing “towers” in a “world” in order to defend the world from “enemies”. Towers try to block enemies from reaching a specific end point on the map by shooting them as the enemies pass. The strategy of the game is centralized around how the user leads to optimal placing and choosing of personal towers. This consists of 4 major components: graphical authoring environment, Game Engine, Game Player, and Game Data. Traditionally, the game engine is separate from the graphical authoring environment, but we intend to develop the ability to live edit games, so our authoring environment will need access to the game engine. The authoring environment will allow the user to define all features of a new game by creating objects using the Game Engine. When all facets of a game have been designed, the authoring environment will convert the created objects into data which can be loaded by the Game Player. After a game has been written to a data file, the Game Player will be used to open said file, create a Game object from it, and pass that object into the engine to run the game. The primary design goals of the project is to create a Tower Defense game engine that can support a predefined path game, a free path game, and a side view game (see example game section below). A key component of Tower Defense games is the basic combat between towers and enemies, so our most extendable features are Tower and Enemy types and behavior. We should be able to add multiple projectiles, firing types, etc. and the user should have the ability to combine predefined objects to create unique compositions. Game ending conditions should also be easily composed of a predefined set of conditions.

#Overview

###Graphical Authoring Environment:

The Author class is the first class to be instantiated. The Author class holds both an instance of GameManager and GameView. It first instantiates GameManager, which represents the back end, and then GameView, which represents the front end. GameManager instantiates all of its classes in the back end on its instantiation and the GameView instantiates all of its classes in the front end using some of the data structures and classes from the back end. The GameManager then has to populate fields in the front end, specifically the EditablesPanel, by reading a properties file which contains a list of all the classes in the GE that implement the Editable interface and then using reflection to find their properties. The GameManager also passes a lambda function to the first instance of EditorView, which lives in the upper right side panel of the UI, created by the GameView. This lambda lets the EditorView call setEditableInGame(Editable). The GameView uses a Prototype pattern to create EditorViews based on the first created instance of the EditorView in order to preserve the setEditableInGame(Editable) lambda function and so that GameView doesn’t have to get it over and over again from the GameManager. Therefore, the GameView passes the lambda functions createEditorViewInTab(Editable) and createEditorViewInPopup(Editable) to its subviews so that they can create new Editor views using the Prototype pattern in either a new tab or as a popup. The most basic interaction between the front end and the back end is where the front end has a back end object that holds its data. The only exception to this is the LibraryData and LibraryView classes, where LibraryView listens to the LibraryData’s ObservableList of Editables.

###Game Engine:

After a person creates a game in the Graphical Authoring Environment and writes it out to a file, the game player has the capability to read in a file and use it to recreate the backend Game object. Assuming the user has selected to play this game in the Player, the Player passes this Game to the View in the Game Engine and calls initializeWorld() on the View (to start the game). At this point, control flow proceeds throughout the Game Engine until the game has completed. An overview of the main parts of the Game Engine are as follows: the View class initializes the GameWorld, and then starts up game with game loop. The game loop is responsible for automatically updating the display by listening to back end objects. View thus relies heavily on backend objects and data. Game is composed of other objects, LevelBoard which allows us to transition through levels, Shop which allows us to keep track of items the user has purchased, and Player which stores all the Player’s current states of the game. One key design decision we made was to have the game engine driven by the front end. As a result of this decision, the game loop will be contained in the front end, specifically in the View. Each iteration of the game loop calls an update() method in the back end game, which steps through the games state one frame at a time. The View contains a mapping between front end and back end objects that need to be displayed; the intent behind this was to have the View recognize user input on JavaFX objects and be able to respond by calling specific methods on objects in the back end. We made it point not to put any JavaFX objects in the backend because we wanted to have the ability to reuse the backend with any other graphical API.
Another pattern we heavily relied on was the Composite Pattern. Many of our backend objects used this pattern which allowed us to separate their behavior from specific class implementation and be able to substitute behaviors, as well as avoid duplicated code. The best example of this is with our GameObjects, which contains a Mover Component, a Physics Component, and a Graphics Component which allows us to support all objects -- Towers, Enemies, Projectiles, etc-- using only one class and switching out components.

#User Interface

The graphical authoring environment user interface will be composed of several panels that will display different aspects of game creation. Referring to our authoring environment UI diagram, the center of the view exhibits an example grid view of when a user would be designing a new level in a tower defense game. The left hand panel represents our library of created objects where the user can double click on anything created in the past to edit it further. Moreover, this panel will separate different types of objects such as terrain, tower, or enemy so that the view is more user friendly to sift through. The right hand side holds two panels. The top panel holds the simple properties of the object that is currently selected. This includes aspects such as the name or the health points. From here, the user is also able to edit the object from this panel. Underneath, the panel will hold all the objects that are able to be edited and the user will be able to select a new object to create from there. The middle portion will also utilize a TabPane so that multiple tabs can be used to represent more complex creations and designs of game objects such as tower. In addition, multiple tabs can be used to create multiple levels simultaneously, if desired. Our authoring environment will also ideally allow more inclined users to utilize Groovy to script their own behavior algorithms if they wish to take the game design to a higher level. A game is represented to the designer through multiples stages of design. The first will be the basic game information such as the game’s name, style of game (set path, free path, etc.), and if it were ever to be scaled to this level, single or multiplayer. Next would be the game preferences such as the number of levels, winning conditions, in game preferences (time based, wave based, etc.). Then, the authoring environment will show creation options. New objects can be created by the lower right panel and everything created can be viewed and further edited through the left panel. This game authoring process is made easier by reducing the amount of code and exposure to technical aspects that the user sees. Moreover, by utilizing tiles, the user is better able to visualize the building of objects. By allowing the path to be created using a pen-like object, this saves the user time from having to select individual tiles to allow them to be walkable. Overall, the large visual aspect of the authoring environment as well as the reasonable amount of latitude given to the user to customize objects allows the user to easily create new games. Any errors such as attempting to build on non-buildable areas will throw errors that tell the user to build elsewhere or to edit the tile.

#Design Details ###Graphical Authoring Environment: Author is the main view class for the Graphical Authoring Environment (or GAE). This class instantiates the GameManager and the GameView classes. The GameView class instantiates the LibraryView, an EditorView that acts as the properties editor in the upper right panel, and the EditablesPanel. The EditablesPanel is instantiated with an EventHandler pointing to the GameView’s createEditorViewInTab(Editable) method, which uses the Prototype pattern to create a new EditorView (based off of the one that the GameView initially instantiated) and place it in a tab. The GameManager class, on instantiation, creates a Game class from the GameEngine (or GE). It then instantiates a PreferencesSet class with the Game object and an EventHandler pointing to GameManager’s getGameBack(Game) method. The PreferencesSet class pops up a view that allows the user to set the preferences of the game. The PreferencesSet class sets properties of the Game object (based on user input) and then passes the object back to the GameManager by calling the getGameBack(Game) method and passing through the updated Game object. The GameManager then passes an EventHandler to the instance of EditorView that GameView created initially. This EventHandler points to the GameManager’s setEditableInGame(Editable) method, so that the EditorView (and all of the EditorViews created from this one using the Prototype pattern) can add their changed Editables to the GameManager’s game object.The GameManager then uses reflection and a properties file to instantiate one of every class in the GameEngine that implements the Editable interface. The GameManager then passes all of these new empty Editables to the Editables Panel, which uses a Prototype pattern so that the player can create new objects. An example of one of these editables is the Tower object in the GE. If the user created a new Tower, the EditablePanel would clone the Tower object is has and then send the new Tower object to a new EditorView, using the EventHandler property that it got on initialization from the GameView. This calls GameView’s createEditorViewInTab(Editable) method which returns a new EditorView class set up with the Editable. The EditorView class uses its EventHandler to call GameManager’s setEditableInGame(Editable) method, which adds the changed Editable to the game object. The Author then creates the first level by first calling createLevel() on the GameManager. The GameManager creates a level for the game by calling a method on the game object that returns a new level object. The GameManager then creates a LevelManager, passing through the level. The LevelManager creates a WorldData which in turn creates a number of TileData instances equal to the number specified by the user at the beginning of the program. Then the Author class calls createLevel() on the GameView. The GameView calls getWorldData() on the GameManager (which in turn calls getWorldData() on the LevelManager) and returns the WorldData. The GameView then uses this WorldData and an EventHandler to its createEditorViewInPopup(Editable) to create a WorldView. The WorldView then uses the getTileDataList() method on the WorldData and its own createTileViews() method to create its Collection of TileViews, each instantiated with a TileData. When TileViews is double-clicked, if it has an Editable, it uses its EventHandler to pop up an EditorView. When an Editable is placed on top of the TileView, setEditable(Editable) is called, which calls setEditable(Editable) in the TileData. Lastly, on instantiation, the GameManager instantiates the LibraryData. The GameView then gets the LibraryData’s ObservableList of Editables and uses it and the createEditorViewInPopup(Editable) to instantiate the LibraryView. The LibraryView listens to the LibraryData’s ObservableList and changes the view as the data does. When a new Editable object is created by the user in the EditablesPanel, and the GameManager’s setEditableInGame(Editable) method is called, the LibraryData’s addToList(Editable) method is also called to add that Editable to the list.

###Game Engine: The key class of the Game Engine is the View Class, which is mainly responsible for the game loop and displaying the data that it receives from the model. The initializeWorld() method in the View starts the game loop and displays the initial graphics, which reflect the game objects it has queried from the Game. The View will manage the map of front end objects to backend counterparts, so that it may call specific objects on backend objects to allow for user input. The View is also listening to these back end objects so that it can automatically update the display to reflect changed in the back end. The main backend class in the engine is the Game class. The Game contains 3 main components: Shop, LevelBoard, and Player. A main component of a tower defense game is the shop, which keeps track of the upgrades, towers, and any other items available for the user to purchase during the game. Our Shop class keeps track of all of these purchasable items, which know their own price and unlock conditions. An observer pattern will be used to determine when unlock conditions are met. The game provides an API method, getShopDisplay(), which can be called by the view to receive all of the information it needs to draw the shop. In order to allow the user to use an object that is bought in the shop, we are using the Prototype Pattern. This means that each game object will implement the Prototype Interface, which contains the clone method. When a user buys an item (through the Shop’s “buy” method), the original item remains in the Shop while an exact copy of the item is placed in the GameWorld. The shop will also implement an isPurchasable(Purchasable item, Player player) which returns a boolean based on whether the player can buy the given item. Additionally, to implement upgrades, we are using the Decorator Pattern. This means that all Upgrades extend the object that they upgrade and change behavior by overriding a method in that class. Implementing the Decorator pattern makes our Upgrade method very extendable; we can create an upgrade at any time that modifies any property or behavior of the existing object. A second important component of the Game is the Player class (not to be confused with GamePlayer). This class keeps track of all information related to the player, such as score, lives, and currency used in the store, as well as the logic to update those fields. Some methods of Player would be changeUnits and getUnits, which allow for updating player points and currently, for example. Finally, the LevelBoard is essentially a manager for the levels. The LevelBoard is responsible for tracking which level is currently being played, as well as progression through the levels, and tracking when a level has ended. One method of LevelBoard is getNextLevel(), which is called during the LevelBoard’s update() method if the current level is completed, and returns the next Level object. Each Level object contains isWon() and isLost() methods, which check EndConditions to determine if they were met. Level also contains a GameWorld and a storyboard of events that occur during the level. GameWorld is the key class here which tracks all of the GameObjects currently in play and has a method, updateSprites(), to update these objects during every iteration of the game loop. The updateSprites() method will call upon the mover, physics, and graphics components of each GameObject to update their state. By creating separate components to dictate GameObject behavior, we have allowed for the easy customization of GameObjects. This avoids the need to create a new subclass of GameObject for each combination of components. In addition, we will be using the state pattern to dynamically change the behavior and animations of GameObjects during play. Mover is responsible for finding the next location for each object. For enemies, the Mover may contain a PathFinder object while most of the Towers will have a null object. However, this implementation allows for extensions such as flying towers with intelligent projectiles. The main job of the Physics Component is to detect collisions between objects. In order to optimize comparing objects for collisions we plan to use spatial partitioning, which will divide the GameWorld into cells so we only have to compare objects within the same cell. Additionally, GameWorld has a grid which has two layers-- a Cartesian plane of doubles and a tiled grid.

#Example games

  1. Set Path Tower Defense (ex: Bloons Tower Defense): The player has an X number of health points based on what the author set the health points as. The health is decremented every time an enemy is able to penetrate the entire path and escape. The path that the enemies travel through is also defined by the author in the authoring environment before the game is actually played. The player wins by surviving a set number of levels that progressively get harder. Newer challenges such as faster enemy waves or stronger enemies will be added per level increase. The graphical authoring environment (GAE) first makes the author set the initial conditions of the game such as the style of the game or the winning conditions. This is done through our PreferencesSet class. Utilizing the EndCondition object from the game engine, the user is able to reasonably set the conditions of winning and losing in their tower-style game. Choices include defining a victory as defeating a certain amount of levels or surviving a certain amount of time. The style of game selected would alter what the design style would be. For a set path tower defense, the authoring environment would no longer function correctly without a path drawn in by the user. After setting the initial conditions such as game style, winning condition, and number of levels, the user can now set properties such as the amount of health the player will have. Specifics such as the types of enemies and waves will be specified in latter portions of the authoring steps. The author will be able to customize new enemies in aspects such as a health, size, or speed and will then be able to compose waves based on multiple enemy types, number of enemies, and frequency. Our Editor class in the authoring environment will use reflection to pull the editable properties in order to create the appropriate GUI for the user to customize game objects with. This will allow the user to create features such as enemies or towers and then use those objects to create levels. After creating game objects, the user will be able to utilize our Tile system to place objects on the grid as they wish. However, utilizing our Path object will allow the user to freely draw the path they desire and adjust the width and height from there. The game engine supports “endCondition”s which notifies the game when an ending condition has been met. In this game, health depletion would be defined as an end condition. The LevelBoard object in the back end checks the ending conditions every iteration of the game loop by calling isWon() and isLost() on the current level object. If isWon() returns true, then the LevelBoard displays the next level or ending screen, and if isLost() is true then it restarts the current level. Path is an object in the back end which can give a next point when queried. The movers of each of the enemies in a path defined tower defense game will query the path to move the enemies.

  2. Free Path Tower Defense (ex: Desktop Tower Defense): In a free path tower defense, the player is able to shape the path of the enemies by setting towers at different places in the world. For example, many players choose to create a maze-like obstacle course in order to maximize the enemies’ time spent in the world, hence giving the player a higher chance of destroying all enemies. Similar to the set path style, player will have a predetermined amount of health. However, this example game’s win condition would be based on the player’s ability to survive 10 minutes of enemy waves. Rather than basing the game’s conditions on levels, we would now have the game depend on time. This time would be able to controlled by a play and pause button that allows the user to select when waves come and go. Many of the same components as the set path style would be applicable to the free path tower defense. Enemies, towers, and other game objects would be created in the same manner but rules would be specified differently. Rather than using levels as indicators of progress, this style of game would use time as a condition to indicate a win or a loss. Moreover, indicating that this is a free path style would prevent the user from ever drawing a path unless the user wanted to change the style of the game. Other than these minor changes, everything else from example game #1 would be applicable. Using the grid object in the back end of the engine, which keeps track of all possible obstacles and free space, a graph search algorithm creates a path from the starting point to the ending point for the enemies to follow, which they query just as they would query a predefined path for the next point. The PathFinder object in the back end will recalculate the path whenever an object is added to the world, which it will be notified via the Grid (observable) that it is listening to.

  3. Side View Tower Defense (ex: Cartoon Wars): In this style, there is one tower that enemies run towards. Rather than utilizing towers to defend a certain path, we are now utilizing weapons such as arrows to defend a single tower. Waves of multiple types of enemies run towards the tower and the player must defend the tower for a set number waves. Projectiles may be fired from the tower. This game differs from the first two types. Rather than an aerial view, this example game will be a side view style. Therefore, there will be less freedom in how enemies are able to move around as they can only move side to side rather than up and down as well. Moreover, the user will be able to customize only a single tower, which will act as the single tower they will be defending in the game. Selecting this in the initial preferences scene will change the available options as the user will no longer be making multiple towers. Our Editor will still be utilized to customize a single tower with whatever properties the user desires and multiple enemies will still be able to be created. Though the view of the Tile objects will be the same, the construction of the game would now differ as there would be a ground level on which enemies will walk on. Yet, though the gameplay differs, our authoring environment would handle this in a very similar fashion in that classes such as the Tower, Enemy, Projectile, and Weapon class would function in the exact same manner but in a different visual and physical setting. Thus, from the authoring point of view, close to nothing has changed in terms of the classes and objects that will be utilized in the creation of a side view tower defense. A tower still functions as a tower and holds the same properties that are editable such as health and similarly, rest of the game objects function in the same way but only differ in their interactions with the world. For example, the physics behind movement would differ but an enemy in a aerial view could still be represented in a side view with the same properties in terms of health or speed. In order to support user-aimed projectiles, the front end of the engine registers a user click at a location/grid cell, and calls a method in the back end fire a projectile at the given location. Additionally, the health feature of the tower is especially utilized in this game. Tower health decreases when enemies run into it, which is detected by the Collision Manager. When collisions are detected, a method is looked up in a table that corresponds to the two objects that collided which updates the game world appropriately.

#Design Considerations ###Graphical Authoring Environment: When structuring the graphical authoring environment, there were a few significant design choices we discussed at length. The first of these was how to populate the various properties for each generic object (i.e. Tower, Enemy, etc) that could be modified by the user. The two possibilities were either to hard code these fields, or to pull in the info from the Game Engine. The main pro of hard coding is the simplicity and ease of coding that it allows, however, we scrapped this idea and decided to go with the second option so as to enable more flexible code. We intend to use reflection and a “Editable” interface to accomplish this. The second design decision we discussed at length stemmed from the first, namely, whether or not to hold a Game Engine object in the graphical authoring environment (GAE). The pros of holding this object are that we are able to populate various property fields based off of the Engine, and that it is necessary for Live Editing (a goal we plan on achieving). The cons are that it reduces the division of the various modules, and that it appears a bit clunky. However, we decided that it was worth including, provided that we keep any cross-module communication to an absolute minimum. The third major consideration was what specific interfaces we need to include. We had established the need for the “Editable” interface, and we reasoned that we would need two more. One to delineate which objects could be built in the world, and one to establish which objects counted as game object. The second part of this design consideration was whether or not to attempt to combine these second two interfaces into a single one. The pros of this are that it would simplify the coding, and more importantly, would do so without significantly altering the design structure. However, we decided to opt for maximum clarity and modulation, and instead will be using two separate interfaces for these two functions. The fourth major design decision we made was whether or not to allow the user to alter the attributes of a specific tower (or other user created object) that had already been placed in the world. There was a great deal of discussion on whether or not to treat this tower as its own separate entity or simply an instance of user-created tower class. For example, if the user were to place a tower of a type they created into the world, the question was: Should they be allowed to alter the attributes (i.e. fire speed, health, etc) of that one specific tower, without changing the overarching class (which would alter all towers of that type), or to not include this functionality, and simply have that tower’s attributes be constant through each instance? The pros of the first option are that it allows for increased customization, the cons being that it would quickly complicate the interface, as you would need to represent each specific tower placed. In the end, we compromised and went with the first option, and decided to implement an accordion style interface for keeping track of each tower, so as to avoid unnecessary UI clutter. The final design consideration we discussed was how to bind our various front-end customization items to their backend counterparts. There were three proposed methods for this, instantiating the variables in the front end and passing them to the back end, instantiating in the backend and passing to the front end, and using a dedicated binding class. The benefit of the first two methods is that the end receiving the instantiated variables would never have to relinquish control of its own variables, while the benefit of the third method is that it would be abundantly clear to anyone reading the code what was happening. We decided to have the variables instantiated in the front end and then pass them to the back end to be bound. Our reasoning for this was one: to limit any excessive passing of variables (which the binding class would have created), and two: that the variables are first altered in the front end, so it makes sense that they should be created there. These five design considerations took up the majority of our discussion time when it came to the GAE.

###Game Engine: The Game Engine subteam discussed the options of using a Cartesian Plane versus a Grid structure to keep track of objects laid out in the GameWorld. We wanted to implement Cartesian Plane because it offers optimal flexibility for the game designer, however, the implementation is tricky because we have to keep track of all of the space that the object covers, which would take up multiple double locations. A grid is nice because there is a predefined number of locations that game objects can occupy, which makes placement of new objects more straightforward. After extensive discussion, we decided that there were important aspects of both, so we would have a layered map of the world-- the bottom layer with doubles and the second layer with a grid. However, we continue to discuss the optimal uses of each one to create the most flexibility for the user. Another design discussion we had was how to handle user input and whether to use a Canvas or JavaFX nodes in the front end. If we used JavaFX nodes, user clicks would be matched to JavaFX nodes (ImageView Objects) in the front end and the appropriate actions would be sent to the back end. Using a canvas, on the other hand, would require the front end to pass the location of user clicks to the back end for matching with a back end object for appropriate handling. If you’re rendering thousands of objects without a canvas, each is its own node and the scene graph has to keep track of all of them which would not be efficient. Despite this positive aspect of canvas, we wanted to take advantage of the ability of JavaFX to recognize individual nodes that are clicked on, so we decided to use JavaFX nodes to prevent unnecessary calls to the back end and to prevent passing coordinates to the back end. We talked at length about how to implement upgrades that users can buy in the game. Our two main ideas were to implement a Decorator Pattern or to simply have components that were switched out. We thought the layering of the Decorator Pattern could be problematic with implementing upgrades that are completely different from the current behavior, and attempting to remove a layer from the middle. Switching out individual components could avoid this, however this would lead to a lot of setter methods and it would be hard to build upon pre-existing methods. Also keeping track of upgrades already applied would have to be tracked externally with swapping components out.

#Team Responsibilities Kaighn: Back end engine, pathfinding
Jeremy: Engine, game object states
Sierra: Engine team, collision engine
Tom: Game Object General Structure, Bounding Boxes
Cosette: front end Game Engine, Game Player.
Nathan: Engine team, Shop (instantiating towers, back-end/front-end communication)
Danny: Engine team, Waves and Events
Brandon: Authoring team, front end features, general UI for authoring environment
Eric: Authoring team, back end features, working with connecting the front end and the back end
John: Authoring team, front end features, working with UI and the editor class that allows user to edit different properties of said game object
Kei: Authoring team, back end features, XML Parsing, GameManager
Nina: Authoring team, back end features, working with library and game data