This week end, I refactored the code of tethical, client and server. All have been pushed on github. Here is an overview of the new code organization :
The server was composed of a big main.py doing all the stuff, with 3 or 4 business classes containing specific algorythms like moves an loading maps. I did not touch these classes, but i exploded main.py into many parts.
main.py is now 80 lines of code. It is pretty close to the hello world server in panda3d documentation, this means it listen to incoming packets with a queued manager, and dispatch the work to controllers. main.py also stores the session.
The code I used to dispatch the work is there:
def processData(self, datagram):
iterator = PyDatagramIterator(datagram)
source = datagram.getConnection()
callback = iterator.getString()
getattr(globals()[callback], 'execute')(self, iterator, source)
Controllers are now stored in a new folder to separate them from the rest of the code (https://github.com/Kivutar/tethical/tree/master/server/Controllers (https://github.com/Kivutar/tethical/tree/master/server/Controllers)). A controller is a python module which is named from the pyDatagram identifier. It get the data from the client, call business classes like Map or Move etc, (in a later time it will query the database, and finally send back a reply to the client(s).
Here is an exemple of controller, the GET_WALKABLES controller, triggered when a client need to know on wich tiles a character can walk:
# Return the list of walkable tile for a character to a client
def execute(server, iterator, source):
charid = iterator.getString()
party = server.parties[server.sessions[source]['party']]
walkables = Move.GetWalkables( party, charid )
server.send.WALKABLES_LIST(charid, walkables, source)
What does it do? First we import the Move business class, wich contains the needed algos. A controller is composed of a single method called execute with allways the same signature: (server, iterator, source)
- server is a referece to the server to access the sessions data.
- iterator is the pydatagram iterator to extract data sent by the client, in this case the client sent the "character id"
- source is a reference to the client who sent the datagram.
The controller invoques a method from the Move module.
Finaly, it sends back a datagram (here WALKABLES_LIST) to the client.
Send.py is a class where I put all the datagrams the server will send to the client. It factorizes the code and hde the datagram complexity. So we can write
server.send.WALKABLES_LIST(charid, walkables, source)
myPyDatagram = PyDatagram()
Now the big part. As for the server, my goal was to explode main.py. So,
- I created a Send.py for the client, similar to what the server now have
- Controllers have been moved out of the main.py, following exactly the organization of the server
I won't waste time describing these changes, please refer to the server side.
Important thing to know: DirectObjects. DirectObjects are a class provided by panda3d. DirectObjects can listen to keyboard keys and bind them to functions, and we can also ask them to stop listening to the keys. In Tethical GUI module, a lot of my classes inherit from DirectObject so they can define what keys they want to hear, and when I don't need them no more, I destroy them and they don't bug me with their binding. Sometimes I also ask them to stop listening, this can be usefull when you have instanciated more than one DirectObject of different kind at the same time. And this is what happens now in the client.
Draws the sky during a battle. Will later contain sequences to change the color, for weather effetcs.
Draws the cursor, move it to a tile, change it's color. Will later contain tasks to animate the pointer with cosinus.
Display, move and hide the Active Time flag on sprites.
Contains functions that returns sequences (animation) usefull during a battle, like battle introduction, attacking, walking, etc...
A very important class to manage the tile matrix during a battle. It can generate a matrix of tile from map data. Draw colored zones by changing the color of these tiles. Find character references stored in these tiles.
KeyboardTileTraverser is a DirectObject managing the moves of the cursor on the tile matrix. It listens to arrow keys, and round and cross button. This KeyboardTileTraverser also contains methods that are only usefull for tile traversing with a keyboard, you wouldn't need to listen to arrow keys if you where using a mouse to select tiles. This means that this class could be replaced if you want to port Tethical to the wii or other pointer or touch devices.
The client will ask the KeyboardTileTraverser to listen to keys in these cases:
- selecting a tile to move a character
- selecting a tile to attack a character
- free mode, when canceling the battle menu
The client will ask the KeyboardTileTraverser to stop listening in these cases:
- displaying dialogs
- displaying a DirectionChooser
- in passive mode (when the other team is active)
CameraHandler is also a DirectObject to manager the camera. It works very similar to KeyboardTileTraverser and is keyboard driven so we may rename it KeyboardCameraHandler. It is enabled and disabled in almost the same phases as the KeyboardTileTraverser, except when displaying a DirectionChooser, the camera remains active.
In this class, I started to move battle specific functions. It is not complete yet. I will maybe move all that is battle related out of the main.py in this class and rename it Battle or BattleMechanics.
This rework of the code took me 4 days. This is pretty short, and I think due to the very small size of the codebase. Python did not come in my way, so it was a good choice for writting a prototype as we need flexibility.
The actual organization of the code is still far from perfect, but some bugs have been fixed, the code have been cleaned and symbols renamed more consistantly and a lot of things are now commented.
Please read the new code and tell me what you like/dislike.
I saw your commits. I appreciate the time you spent on it. I'll get my test event script and my work-in-progress event test platform into the repository. Neither of these are very complicated, but I hope to eventually end up with a lot of example code for eventing to fall back on later.
I tried your event test platform, it worked like a charm and i am now curious to see more tests.
Concerning animated water, I think we can achieve the same effect using this method http://www.panda3d.org/manual/index.php/Automatic_Texture_Animation without scripting. But i am not certain. It may be also usefull for sprite animation, I mead getting rid of sprite2d.
Yes, that method will also work. However, I'm concerned about people needing to use the command-line tools and losing interest because of this. We at least have an alternative to that. Once I get events displaying in the control panel, there will also be no need to see the command-line for events, considering there will be a button for launching the event and the operating system can be told not to use a shell window. I may also include a "debug" option the intentionally shows the shell window. I have several map-related concepts planned for some of the maps I've designed, but I don't know if you want them in the "fft" directory.
The Lunar Core map fits the Final Fantasy theme, and I plan to include an event that causes the crystal to glow, the shadows cast by the crystal on the columns to extend/contract, and then overall to change the color scale of the top part of the map to make it look like it's actually affected by the glow effect (as a fake light source). I would also like to demonstrate setting up a playlist of songs (a Panda3d Sequence of some kind to crossfade between these two tracks: Lunar Core (http://darkabstraction.com/showOff/ffhackticks/music/Lunar Core.ogg) and Night Landing (http://darkabstraction.com/showOff/ffhackticks/music/Night Landing.ogg)) to exist in place of the map designation. Similarly, some of the songs I've prepared for my maps have loop points within the song I'd like to show how to take advantage of but not for this map.
For the zodiac signs map I made, I want to show how to get the list of walkables and do graphics-related things with that information. Later, I want to add a hook into the game to allow code to be run between when walkables are received and displayed so that characters with bad zodiac compatibility cannot walk on squares with such signs. Similarly, I'd like to provide a bonus to squares that the character has good zodiac compatibility with, but I feel like that will be difficult. If nothing else, perhaps the squares will have sound effects. I also want to animate the geometry of the clock, which should be quick.
Those are just some things I have planned for my stuff. Other ideas I've had in general are inspired from Street Fighter's early games where the "map" was animated behind the actual action. I think things like birds and people existing within the context of the map could be interesting. Good candidates for such a thing are the fish in the custom006 map, but I haven't thought up anything good to do with them yet. Maybe they can behave like the scene from FF6 with Celes and Cid, or maybe they can come up to the surface of the water to feed off what may be floating up there every once in a while (and this could be denoted by a water ripple like I wrote for rain).
Quote from: lirmont on May 09, 2012, 11:09:35 amThose are just some things I have planned for my stuff. Other ideas I've had in general are inspired from Street Fighter's early games where the "map" was animated behind the actual action. I think things like birds and people existing within the context of the map could be interesting. Good candidates for such a thing are the fish in the custom006 map, but I haven't thought up anything good to do with them yet. Maybe they can behave like the scene from FF6 with Celes and Cid, or maybe they can come up to the surface of the water to feed off what may be floating up there every once in a while (and this could be denoted by a water ripple like I wrote for rain).
Like the overworld map in Chrono Trigger, where there are clouds floating by and birds flying overhead (some bird sounds?), along with a mild breeze shaking trees and making noise?
Wouldn't it be hard to animate these things, within the confines of 2D sprites?
Birds can be composited over top as a final step (Panda3d has solid features for this), and trees have actual geometry (so their textures can cycle and I've provided code for how this works with the example event already).
Yes the GUI is also there to hide too much complexity to new users. I like your ideas of events, why not putting an 'experiment' or 'lirmont' folder in the client, similar to 'fft' and 'lijj', to add your maps and events?
Lijj told me the sprites he started to draw at high resolution are too much work and he won't be able to finish, so we agreed on using fft resolution to provide 3 basic jobs and 3 basic maps as sample open source content to get people started with something very classic. For this I plan to add a 'sample' folder in the client to store this game. This will also contain the open source sounds etc.
Concerning extending the code, we have to choose a strategy. We can build a very simple and generic hook system and put hooks everywhere in the code. Or allow game folders to override python modules of the same name, like done in this framework http://codeigniter.com/user_guide/general/creating_libraries.html (see the "Replacing Native Libraries with Your Versions" part). I have absolutely no idea of which way is better. I used both of them.There may be another good ways to provide extensibility. What do you think on the subject?
I don't think it's a bad thing to allow someone to override files like that. I think it will have a purpose for some people who want to change some very small details for one game. For eventing, we need to adopt an event model. As CoderBrandon mentioned elsewhere, Panda3d has one already. In other words, it's just a matter of having the engine code emit those events to acheive the purpose (in most cases). Those events need to emit the appropriate data, too, like a reference to the character that moved and where they moved for something like a OnCharacterMove event.
For engine additions, without a separate plugin system, using work you've already done will likely be a hassle, unless you use it exactly the same way between all of your games (in which case, you could just overwrite the original files). With a solid event system in place, a plugin could just hook into appropriate events during state changes (ex. loading -> login screen -> game selection -> map picker -> map).