Software architecture, packages, and classes

Issue #4 new
Jose Martinez
created an issue

Yeah this is a little bit broad but we need a place to discuss these things and didn't want to create an issue for each (just yet).

Comments (9)

  1. Jose Martinez reporter

    I propose a package called "unit" where we can part our BadGuy and other units that materialize. From previous experience I think we might need a class called something like CollifableUnit that is able to get hit and lose hitpoints and die. This might be desired because BadGuy might not be the only one to have these attributes. We might have objects like Structures that are part of the scene that can be targeted and take damage and die.

    If no objections I will proceed forward with this.. we can always change it later.

  2. Daniel Zwolenski

    I'd prefer 'Sprite' over 'Unit' as people know what a Sprite is in this context - but I don't mind too much. In the following you can change Unit with Sprite if you want.

    If we get the some very basic inelegant stuff going first we can then refactor out re-usable Sprites (like colllifabile, movable, whatever) later. Start lean and agile then refactor mercilessly as needed would be my vote.

    As a very simple starting point how about the following classes (not fussed on packages, can have them or not at this point):

    • Sprite extends javafx.Group
    • BadGuy extends Sprite
    • Tower extends Sprite

    • Level extends javafx.Group

    • LevelFactory (interace)

    • DefaultLevelFactory implements LevelFactory

    • SimpleLevel extends Level

    'Level' represents a map. It has an exit and entry point (just simple 2D coordinates) and a collection of Towers and a collection of BadGuys.

    A Level.start() method can start a thread which creates a BadGuy every 'x' seconds (or whatever that Level wants to do) and adds that BadGuy to the Level at the entry point (i.e. just off screen to the left).

    When a BadGuy gets added to a Level he works out the path to the exit point. Let's start by ignoring towers and other blocks and just make a bee-line straight to it. The BadGuy creates an Animation that continually moves him towards the exit point along the path he worked out. When the Animation finishes, the BadGuy removes himself from the Level. Let's worry about shooting and dying later.

    The LevelFactory is used to create Levels and we can provide concrete implementations that we plugin with more BadGuys and whatever as we beef things up. Eventually the LevelFactory could read from a level file or something.

    Our startup code could then just be (pseudo code):

         LevelFactory levelFactory = new DefaultLevelFactory();
         Level levelOne = LevelFactory.createLevel(); 
         rootOfScene.setCenter(levelOne);
         levelOne.start();
         Scene scene = new Scene(rootOfScene);
         stage.show(scene);
    

    If we can have a first milestone of a Level being shown with BadGuys (happy for them to be coloured circles or triangles at this stage) walking from an entry point to an exit point then that would be a good start.

    After that maybe we can add a MouseHandler to the Level to add a new Tower every time we click somewhere (eventually adding a TowerFactory). This then fires a TowerAddedEvent which causes the BadGuys to all recalculate their route (taking into account the new towers in the way).

    That'd be my starting point and very happy if someone (Jose?) wants to have a crack at that while I finish off the doc on how to setup a dev environment.

    The design above is deliberately rough and ready to get something up and running quickly. Then we refine after that.

    I'm also assuming that we don't need the old fashioned update-paint loop and can get away with putting an individual animation on each Sprite. Maybe Richard or someone who knows the performance impacts of this could comment. It would be an interesting test at any rate to see what happens when we have 1000 BadGuys, each with their own animations!

    The floor is definitely open to other ideas - shout out or chuck up some code. I'd say don't be shy to just have a crack at something, but also don't be precious if we then refactor it completely (I definitely won't care if someone rewrites anything I put up - get Agile!).

  3. Daniel Zwolenski

    I had a crack at this, the source code as per my last post is now in the repo.

    Basically we just have a bunch of blue squares marching across the screen from one corner to the next. Very simple stuff.

    Check it out and see what you think.

    Richard - would be great to get your input on the general approach of Animation usage with respect to performance when you get a chance.

    The code (and any code I put up) is free for complete reworking if anyone has better ideas. I don't get attached to my code - add/improve/cull however you think best.

    For me, the next logical point would be to handle mouse clicks on the level and add Towers to it. Should be a very simple addition for anyone wanting to try something.

    If you wanted to get fancy you could have a TowerFactory (interface) and then a list of clickable buttons or menu options off to the side of the scene (e.g. off to the right) where clicking one selects a specific TowerFactory on the current Level. Then when you click on the Level it could just call this.towerFactory.createTower() and add whatever tower it gets back at the X,Y that was clicked.

    If it was me I'd start with some pretty simple Tower implementations (RedTower, GreenTower, etc) and we can add fancy TowerFactories later (i.e. nuclear cannon of destruction or whatever).

    Path routing would be after that (i.e. BadGuys walking around, rather than through towers) and then shooting I reckon. After that it's all about funky, complex BadGuys, Towers, Levels, sounds. point scoring, and making it pretty.

    And then deployment :)

    But this is all just my suggestion - feel free to forge your own path if you have better ideas!

    I might be into it a little bit tomorrow but I'm out all day rock climbing on Sunday.

  4. Jose Martinez reporter

    Dan,

    Hey I'm about to give it a shot, going to go through your Netbeans tutorial.

    Sprite should not be the top level class for our units. A Sprite is a 2d graphical entity. If a class Sprite does exist it will be an attribute (has a) of other other units. So for example BadGuy and Tower, which might or might not inherit from the same parent will have an attribute called visualRepresentation. This visualRepresentation will be of type Node or Group or something more custom. This allows visualRepresentation to be 2d or 3d or vectors or whatever else we can think up.

    Level should not create the units in real time. They should be created before hand. We need all the CPU for graphics not instantiation.

  5. Jose Martinez reporter

    Ok I got the code in my local project but none of the javafx stuff works. I think I need to manually add the 4 javafx jars that I always have to add since Netbeans 7.2, deploy.jar, javaws.jar, jfxrt.jar, and plugin.jar. In the project seems like I have to add them via Maven.

    So two questions... shall we add these 4 jars to the Maven dependency? And if we do, which version of javafx should we get them from? Maybe the problem is with my configuration of Netbeans. I'll keep messing with it.

    Product Version: NetBeans IDE 7.2 (Build 201207171143) Java: 1.7.0_10-ea; Java HotSpot(TM) Client VM 23.4-b01 System: Windows 7 version 6.1 running on x86; Cp1252; en_US (nb)

  6. Daniel Zwolenski

    Jose and I have had a quick chat directly and his NetBeans issue is sorted. Just for reference - you don't need to add any JARs to the POM, etc. Just do the mvn jfx:fix-classpath command. You need to restart NetBeans after running it. I've included this in the doc but if your project doesn't just magically work for you there's something wrong so email me direct (zonski@gmail.com).

    Regarding the code base, I have made some additional enhancements and refactored a few things.

    Firstly, I have added some basic "chrome", i.e. the UI that goes around the game which lets you start new games, pause the game, progress to the next level, etc.

    Which means there is now a progression of levels, so when you complete level 1, you move onto to level 2, etc. Since you can't place towers yet, the level ends when all the bad guys (5 per level) make it to their destination point.

    In doing this I also introduced a "Game" class, which encapsulates info about the game world (it's more or less acting as the Game "controller"). With this Game class, the LevelFactory became a bit redundant so I dropped it and just merged this logic into a createNextLevel() in the Game class - keeping it simple.

    Part of my goal with this was to work out how to make pause work without an update-render loop as in classic 2D gaming. I ended up adding a 'pausedProperty' to the Game that all the Sprites (and the Levels) listen to and play/pause their internal animations to match. Seems to work - although I reworked the BadGuy animation in the process to provide the basics for path routing (which is what I will look at next but probably not till next week).

    I also moved things into some sub-packages since we're starting to get a few classes now.

    All open to debate and update.

    Regarding Jose's two other points:

    Sprite vs Unit: I am following Richard's suggestion of not worrying about a view/model separation for this in the short term. There is nothing in the current design that stops us from using alternate visualizations, we can add whatever we want to the Sprite.getChildren() since it is just a "Group" (the most primitive type of Node in JFX), so we can add SVG, Images, trees of sub-animating nodes, whatever.

    It's not that I am not aware of the benefits of the separation or know what should be done (this is definitely not my first run at game programming) but once we start getting into these sorts of abstractions the discussions around them will take a lot longer, as we move into "what makes a good Sprite library" instead of "what does this game need to work".

    My thinking (and I'm guessing this is what Richard was suggesting) is that we knock this first thing up quickly, with simple, low-abstraction design. Then we step back, look at it and work out what a nice clean design would look like having a concrete reference point. I could see a JFX Sprite library coming out of all this as a separate project but we may as well do that properly and with care once this is working rather than try and build a re-usable Sprite library up front.

    We need to get it running it quickly so that Richard will have a game to demo on the Android/iOS version of JFX ;)

    Regarding the second point on pre-instantiation vs on-the-fly instantiation. Good point, I am happy either way (and wouldn't mind testing both to see what happens). The Level class has been deliberately made as a base class to be extended. We have SimpleLevel at the moment that does the on-the-fly instantiation, but feel free to add another alternate Level implementation that does the pre-instantiation option.

    Though if you're keen to get your hands dirty with the code I would still suggest the Tower placement side of things. Should be a case of creating a TowerFactory interface, hooking this up to the ToggleButtons in the toolbox, then on Mouse click calling Level.addTower(factory.createTower(), x, y). Bonus would be to make some cool towers that have some internal animations on them (e.g. glowing, spinning, pulsing, whatever).

    If anyone else is thinking of having a crack at anything probably best to shout out first so we can section off jobs. This issues stuff is a bit annoying - a mailing list would be better - but I guess comment here and we'll go from that.

  7. Jose Martinez reporter

    I noticed the bind and listeners. I'm not sure these are good to do with games. I have had bad performance in the past with them. For example, while having a listener for the paused property is elegant and slick, its the first thing I would remove if there are performance problems. Others can chime in also on what they think about this, but in general I try to stay away from listeners and instead notify the elements that need to know about specific event when they happen. But because the pausedProperty listener is a more elegant way, I would only remove it if we need to improve perf. Just giving a heads up that I do not trust listeners and binds.

  8. Daniel Zwolenski

    Hey Jose,

    Good work on getting some code in there! I had a bit of a look through but will need to look deeper at it to work out the gist of what you're going for.

    The Wave idea is good but I haven't quite worked out the full relationships between Wave, WaveMaker, WaveSlot, BadGuyFactory, LevelFactory, etc and why we need all of them. One thought is that we may be working towards cross-purposes in the area of abstraction. I'm deliberately trying to keep it very unabstracted with minimal factories and abstract base classes. I think you are trying to make a more generic game engine. Both are legitimate targets to aim for but we should decide up front what our goals are so we're all aiming for the same thing.

    Richard - would be great to have your input on what you want this to project to be and what the initial goals are? Do you want a base "game engine" or just a simple game at this stage? Any input on the current/proposed architectures would be useful.

    Regarding the Grid idea, Jose, can you outline what the Grid gives us over just being able to place towers inside defined areas? I have used Grid-based systems to good effect in other games but mostly to improve path-routing and AI. If we go for option 2 above, what does the Grid give us, since the Paths are effectively pre-defined for each map (i.e. can be saved into the Map file when it is created)?

    I was envisioning the Map to have defined areas (shapes) where Towers can be put and places where they can't. BadGuys would follow a pre-defined path for that Map that lead them around the Tower areas so we'd never have any kind of collision. The logic behind Tower placement would just see if the Tower fit in the space where the user clicked or not (checking it also doesn't overlap with existing Towers). I'm not sure what the Grid would give us above this simple model, but maybe I am missing something?

    Once we get the basic code working, I'd see Levels being loaded from a file (possibly FXML), and this file would contain all the Wave, Path, BadGuy information saved directly into it. The code then just loads a Level file and calls "start" on it.

    I did a bit of work around Towers and Shooting last night that works on the SimpleLevel but the Wave stuff didn't quite work (I think you have problems in there regarding pausing and new game logic) so I haven't checked in yet. I will have another look tonight but probably best to give me a shout if you are planning on doing anything more on it so we don't clobber each other.

    There's a bit of C/C++ coding styles creeping in from your code too (like a true game programmer!), we may have to Java up your code a little bit more :) (e.g. the CONSTANTS file) but that is purely stylistic and very minor at the moment!

    Good to have our first collaborative merge though, nice one!

    Dan

  9. Jose Martinez reporter

    Dan,

    Grids and Cells: Grids have many benefits in a tower defense game. Towers get placed in predefined squares (or as i call them, cells). This is a pretty standard by now, and even from when the first tower defense games that I played on Starcraft (check out Tower Defense on the Ipad, Field Runner, Sentinnel on the Ipad, and ibomber for the Ipad). These cells make up a grid on the map and the grid is standard across all maps... that is to say that a cell on the grid with coordinates (4, 4) will be in the same location on the screen regardless what map you are playing on. So now a map can be described by a series of cell types and grid locations. A TowerCell can have attributes that will allow the user to interface with it for the buying and selecting of a tower. A TowerCell for example would have mouse over that shows the user that this is a location where a tower can be built.

    Besides standardizing the placement of towers there could be various types of cells should we want to implement them. For example some cells can be used to describe enemy paths, which is what I did in this game but I did not create an EnemyPathCell as I did not see a need for it at present moment. Also, cells can be used to place structures and obstacles. Another common attribute that many tower defense games have is altitude of the land. When weapons are shot from high altitude location (like top of a hill) they get a range bonus. We might not wish to implement these other advanced features but doing so would not be hard to now that we have Cells and Grids established.

    Some games have started displaying the grid visually as it makes for an improved interface. For example with a visually displayed grid you can tell where you can build and where the enemy path is.

    Factories: By now using factories is pretty much the default for things like Levels and Units and stuff like that. Not using them would, IMO, creates a mess. Factories encapsulate away messy implementation details that client objects do not need to deal with. With proper factories in place we can create levels using FXML, XML, hard code, download from internet, or what ever other method we want. If the creation of these objects do not go in factories then what ever object does create them can be called a factory or its doing more than one thing.

    Waves: Waves, like the grid, is another real physical thing in the game world that needs to be to programmable and expressed in the code. I think all tower defense games, I could be wrong here, have waves. I will change WaveMaker to be WaveBuilder and will changes its methods to fit into the Object/ObjectBuilder model that is used in JFX. Waves consist of slots. A wave may have various units coming into the screen from various paths. So a wave needs to support multiple paths. Also waves need to have the concept of slot. Slots are very important when messing with waves because they give you the ability to control unit density. A slot can sit across multiple paths. So a wave with three paths will have slots that go across all three paths. This keeps the units insynch as they came out and lets you control any patterns you may want to implement..... for example the first ten slots could be 1 unit each in path 1, followed by a few empty slots, then 10 more slots with units in path 2, followed by 10 slots with 1 unit in path 1 and 1 unit in path 2 each. So this creates a pattern and also a way to describe how this wave of 40 units will come onto the board. 10 units in path1, followed by 10 units in path2, followed by 20 units side by side in paths 1 and 2.

    Constants: I am not married to the class CONSTANTS. I just hate having constants being defined in regular classes and really do not have a strong preference as to where they should go. But I do think is we want to move away from the CONSTANT class then lets come up with something soon before it becomes too entrenched. Maybe some global property beans.

  10. Log in to comment