Cabo Trafalgar will have original music for next version
Thanks Enrique Gomez-Cabrero Fernandez for your gift.
https://soundcloud.com/enrique-gomez-cabrero
Cabo Trafalgar
jueves, 7 de enero de 2016
martes, 1 de septiembre de 2015
Templated Nifty XML with FreeMarker
I finally managed to add a most missed feature (in my opinion) in nifty-gui, though I had to add it on top of my nifty-flow add-on.
Benefits, quite clear to me, for (more or less) the same screen, let me show you the comparison on code and difference in the paradigm. It also allows you to focus in a single way of working and not needing to learn two ways of making the same job, including style. I hate working on front end.
OLD VERSION, Using Java Builder because there's a dynamic number of lists to render.
NEW VERSION, two files, some preparation of data in the Generator
PLUS the XML with some extra intelligence
Please notice the use of Freemarker commands like list or .counter.
Already uploaded to version 0.4.0 in https://github.com/albertonavarro/nifty-flow
Benefits, quite clear to me, for (more or less) the same screen, let me show you the comparison on code and difference in the paradigm. It also allows you to focus in a single way of working and not needing to learn two ways of making the same job, including style. I hate working on front end.
OLD VERSION, Using Java Builder because there's a dynamic number of lists to render.
public final class SelectKeyboardControlsScreenGenerator implements ScreenGenerator { @Autowired private Nifty nifty; @Autowired private SelectKeyboardControlsScreenController controller; /** * Singleton */ @Autowired private GameConfiguration gameConfiguration; @Override public void buildScreen(String screenUniqueId) { if (nifty.getScreen(screenUniqueId) != null) { nifty.removeScreen(screenUniqueId); } buildScreenNow(screenUniqueId); } public void buildScreenNow(String screenUniqueId) { Collection<KeyboardCommandStateListener> keyListeners = gameConfiguration.getPreGameModel().getByType(KeyboardCommandStateListener.class); //sorting commands in alphabetical order of command name List<KeyboardCommandStateListener> sortedCommands = newArrayList(keyListeners); Collections.sort(sortedCommands, new Comparator<KeyboardCommandStateListener>() { @Override public int compare(KeyboardCommandStateListener o1, KeyboardCommandStateListener o2) { return o1.toString().compareTo(o2.toString()); } }); final PanelBuilder outerPanelBuilder = new PanelBuilder("PartitionPanel") { { height("80%"); childLayoutHorizontal(); } }; final PopupBuilder popupBuilder = new PopupBuilder("popup") { { text("some text"); } }; List<List<KeyboardCommandStateListener>> partitionedSorted = Lists.partition(sortedCommands, 4); int partitionIndex = 0; for (List<KeyboardCommandStateListener> currentPartition : partitionedSorted) { final PanelBuilder partitionPanelBuilder = new PanelBuilder("Partition" + partitionIndex++ + "Panel") { { height("80%"); childLayoutVertical(); } }; for (final KeyboardCommandStateListener currentCommandListener : currentPartition) { final PanelBuilder commandNamePanelBuilder = new PanelBuilder(currentCommandListener.toString() + "Panel") { { childLayoutHorizontal(); text(new TextBuilder("text") { { text(currentCommandListener.toString()); style("nifty-label"); alignCenter(); valignCenter(); height("10%"); margin("1%"); } }); control(new ListBoxBuilder(currentCommandListener.toString()) { { displayItems(4); selectionModeSingle(); optionalHorizontalScrollbar(); optionalVerticalScrollbar(); alignCenter(); valignCenter(); height("10%"); width("10%"); margin("1%"); } }); } }; partitionPanelBuilder.panel(commandNamePanelBuilder); } outerPanelBuilder.panel(partitionPanelBuilder); } Screen screen = new ScreenBuilder(screenUniqueId) { { controller(controller); // Screen properties // <layer> layer(new LayerBuilder("Layer_ID") { { childLayoutVertical(); // layer properties, add more... // <panel> panel(outerPanelBuilder); // </panel> panel(new PanelBuilder("Panel_ID") { { height("20%"); childLayoutHorizontal(); control(new ButtonBuilder("PreviousButton", "Back") { { alignCenter(); valignCenter(); interactOnClick("back()"); } }); control(new ButtonBuilder("NextButton", "Next") { { alignCenter(); valignCenter(); interactOnClick("next()"); } }); control(new LabelBuilder("RepeatError") { { alignRight(); valignCenter(); text(""); width("50%"); color(new Color("#ff0")); } }); } }); } }); // </layer> } }.build(nifty); }
NEW VERSION, two files, some preparation of data in the Generator
public final class SelectKeyboardControlsScreenGeneratorXML extends FtlTemplateGenerator { /** * Singleton */ @Autowired private GameConfiguration gameConfiguration; public SelectKeyboardControlsScreenGeneratorXML(Nifty nifty) throws IOException { super(nifty, "/mod/common/interface_keyboardselector.xml"); } @Override protected Map injectProperties() { Collection<KeyboardCommandStateListener> keyListeners = gameConfiguration.getPreGameModel().getByType(KeyboardCommandStateListener.class); //sorting commands in alphabetical order of command name List<KeyboardCommandStateListener> sortedCommands = newArrayList(keyListeners); Collections.sort(sortedCommands, new Comparator<KeyboardCommandStateListener>() { @Override public int compare(KeyboardCommandStateListener o1, KeyboardCommandStateListener o2) { return o1.toString().compareTo(o2.toString()); } }); final List<List<KeyboardCommandStateListener>> partitionedSorted = Lists.partition(sortedCommands, 4); return new HashMap() {{ put("partitionedKeyboardCommands", partitionedSorted);}}; } public void setGameConfiguration(GameConfiguration gameConfiguration) { this.gameConfiguration = gameConfiguration; } }
PLUS the XML with some extra intelligence
<?xml version="1.0" encoding="UTF-8"?><nifty xmlns="http://nifty-gui.sourceforge.net/nifty.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://nifty-gui.sourceforge.net/nifty.xsd http://nifty-gui.sourceforge.net/nifty.xsd"> <useStyles filename="nifty-default-styles.xml" /> <useControls filename="nifty-default-controls.xml" /> <screen id="${screenUniqueId}" controller="com.navid.trafalgar.mod.common.SelectKeyboardControlsScreenController"> <layer id="background" childLayout="center"> </layer> <layer id="foreground" childLayout="vertical" style="nifty-panel-no-shadow"> <panel id="panel_top" height="15%" width="75%" align="center" childLayout="center"> <text text="Select the keys..." font="Interface/Fonts/Default.fnt" width="100%" height="100%" /> </panel> <panel id="panel_mid" height="70%" width="75%" align="center" childLayout="horizontal" style="nifty-panel-red"> <panel id="panel_mid_left" width="100%" align="center" childLayout="horizontal"> <#list partitionedKeyboardCommands as partition> <panel id="partition_${partition?counter}" width="50%" align="center" childLayout="vertical"> <#list partition as keyboardCommands> <panel id="keyboardCommands_${keyboardCommands}" align="center" childLayout="horizontal"> <control id="${keyboardCommands}" name="listBox" displayItems="4" forceSelection="true" horizontal="off" width="30%" align="left" /> <text text="${keyboardCommands}" font="Interface/Fonts/Default.fnt" align="right" /> </panel> <text text="" font="Interface/Fonts/Default.fnt" align="right" /> </#list> </panel> </#list> </panel> </panel> <panel id="panel_bottom" height="15%" width="75%" align="center" childLayout="horizontal"> <panel id="panel_bottom_right" height="50%" width="25%" valign="center" childLayout="center"> <control name="button" label="Back" id="QuitButton" align="center" valign="center" visibleToMouse="true" > <interact onClick="back()"/> </control> </panel> <panel id="panel_bottom_right3" height="50%" width="25%" valign="center" childLayout="center"> <control name="button" label="Select ship" id="PlayButton" align="center" valign="center" visibleToMouse="true" > <interact onClick="next()"/> </control> </panel> </panel> </layer> </screen> </nifty>
Please notice the use of Freemarker commands like list or .counter.
Already uploaded to version 0.4.0 in https://github.com/albertonavarro/nifty-flow
sábado, 29 de agosto de 2015
Input processing in Cabo Trafalgar (and 2)
Another of the risks to face when creating Cabo Trafalgar is making the inclusion of any new element expensive, in terms of work needed due to interactions with elements already existing in the game.
If you didn't design the way of creating new ships correctly, you might end up adding code per command relative to every possible different input available in the system. Likewise, you don't want to modify your ship's commands if you added a new input to your game.
Keeping the work in O(1) is paramount (yes, complexity can be applied to you as a worker).
My strategy for solving this problem is converting one 2-dimensional array into two 1-dimensional ones. Conceptually like this:
In plain English, what this mean is that I decouple actions from actions consumers.
On one side, all I care when I create a new Ship is that it creates "Commands", these are classes able to execute Ship's functions, they represents what a user might want to do. What a ship might or might not do will be determined by the collection of Commands it returns and nothing else. O(1).
On the other side, all I care when I'm creating a new input adapter is that it's able to use commands, generically. It's a simple interface with an execute, so I can focus in the WiiFit complicated bluetooth bridge or the Remote Controll connection, without needing to thing on the other end of the command. O(1)
This is the design implemented in Cabo Trafalgar with the flexibility and "inexpensiveness" in mind.
The story beings with a set of Command, provided by a particular ship (see AShipModelInteractive in the previous post http://cabo-trafalgar.blogspot.co.uk/2015/08/input-processing-in-cabo-trafalgar.html), these are the functionality our ship offers and we need to map it to inputs.
The first step is asking GeneratorBuilder what CommandGenerators are there available for each Command. A CommandGenerator represents a different input, they must be registered in GeneratorBuilder declaring what subclass of Command they support (so far, all CommandGenerators support all Commands, I haven't found an example of subclassing).
So here we have KeyboardCommandGenerator, registered in the GeneratorBuilder (using "registerBuilder" and declaring itself available for each Command it's asked.
As a user, we asked for CommandGenerators for some commands, and we retrieved a list of CommandGenerators per Command, now if that list was longer than 1, we should decide per command how we're going to use it. In this example, we have only KeyboardCommandGenerator so there's little point to choose, only one option per command.
Now we have a command and a "chosen" CommandGenerator, it's time to associate them by invoking on generateCommandStateListener, it takes the Command now, they keycode comes later.
Now we have a collection of KeyboardCommandStateListener, it's time to associate it with keys, up to the user, and the job's done. Each time that key is pressed, the Command in the Ship will be invoked.
The advantage of this design is the ease of adding more CommandGenerators, as I have a screen for choosing the right CommandGenerator per command, I can mix them in the same game.
If you didn't design the way of creating new ships correctly, you might end up adding code per command relative to every possible different input available in the system. Likewise, you don't want to modify your ship's commands if you added a new input to your game.
Keeping the work in O(1) is paramount (yes, complexity can be applied to you as a worker).
My strategy for solving this problem is converting one 2-dimensional array into two 1-dimensional ones. Conceptually like this:
On one side, all I care when I create a new Ship is that it creates "Commands", these are classes able to execute Ship's functions, they represents what a user might want to do. What a ship might or might not do will be determined by the collection of Commands it returns and nothing else. O(1).
On the other side, all I care when I'm creating a new input adapter is that it's able to use commands, generically. It's a simple interface with an execute, so I can focus in the WiiFit complicated bluetooth bridge or the Remote Controll connection, without needing to thing on the other end of the command. O(1)
This is the design implemented in Cabo Trafalgar with the flexibility and "inexpensiveness" in mind.
The story beings with a set of Command, provided by a particular ship (see AShipModelInteractive in the previous post http://cabo-trafalgar.blogspot.co.uk/2015/08/input-processing-in-cabo-trafalgar.html), these are the functionality our ship offers and we need to map it to inputs.
The first step is asking GeneratorBuilder what CommandGenerators are there available for each Command. A CommandGenerator represents a different input, they must be registered in GeneratorBuilder declaring what subclass of Command they support (so far, all CommandGenerators support all Commands, I haven't found an example of subclassing).
So here we have KeyboardCommandGenerator, registered in the GeneratorBuilder (using "registerBuilder" and declaring itself available for each Command it's asked.
As a user, we asked for CommandGenerators for some commands, and we retrieved a list of CommandGenerators per Command, now if that list was longer than 1, we should decide per command how we're going to use it. In this example, we have only KeyboardCommandGenerator so there's little point to choose, only one option per command.
Now we have a command and a "chosen" CommandGenerator, it's time to associate them by invoking on generateCommandStateListener, it takes the Command now, they keycode comes later.
Now we have a collection of KeyboardCommandStateListener, it's time to associate it with keys, up to the user, and the job's done. Each time that key is pressed, the Command in the Ship will be invoked.
The advantage of this design is the ease of adding more CommandGenerators, as I have a screen for choosing the right CommandGenerator per command, I can mix them in the same game.
Input processing in Cabo Trafalgar
If you haven't read multiplayer-modes-in-cabotrafalgar, you should do it now, as "mode 0 - 3" are explained there, otherwise you won't fully understand this article.
This should be the first of a series of articles to explain how to extend Cabo Trafalgar with more and different controls, modules and ships (these are, not by chance, the three dimensions Cabo Trafalgar relies on).
The aim of this article today is explaining why it's not a good idea to rely in the Jme3 Input Manager directly if you're planning to have several multiplayer modes like Cabo Trafalgar does.
In little words, we need to cover the following cases:
Player's input to local game
Player -> Keyboard Input -> Ship calculations locally -> Render locally
Ghost representing past game by same (local file) or (recordserver online service) another player
Downloaded game -> Ship position is set externally -> Render locally
Remote input to local game (multiplayer mode 1)
Remote player -> Remote input -> Ship calculations locally -> Render locally
Multiplayer (multiplayer mode 2 and 3)
Player -> Keyboard Input -> Transmitted to server
and
Server position calculations for all ships -> Ship position is set externally -> Render locally
Player's game is stored in local file or remote server for future ghost/reference
Player -> Keyboard Input -> Ship calculations locally -> Store to file / server
As you can see, if you want to cover at least two of these modes, you need to detach the following elements and combine them according to your needs:
Renderization (AShipModelZ): What we render is a representation of a ship (and other elements), whose position, rotation and scale is known to us, but not how or where they have been calculated. They can come from:
Input will deserve another post itself, as it can be more complicated than it seems :)
In the diagram below, green classes belongs to the api, no real meaningful functionality on them, in yellow, ShipModelZ implementation of them:
This should be the first of a series of articles to explain how to extend Cabo Trafalgar with more and different controls, modules and ships (these are, not by chance, the three dimensions Cabo Trafalgar relies on).
The aim of this article today is explaining why it's not a good idea to rely in the Jme3 Input Manager directly if you're planning to have several multiplayer modes like Cabo Trafalgar does.
In little words, we need to cover the following cases:
Player's input to local game
Player -> Keyboard Input -> Ship calculations locally -> Render locally
Ghost representing past game by same (local file) or (recordserver online service) another player
Downloaded game -> Ship position is set externally -> Render locally
Remote input to local game (multiplayer mode 1)
Remote player -> Remote input -> Ship calculations locally -> Render locally
Multiplayer (multiplayer mode 2 and 3)
Player -> Keyboard Input -> Transmitted to server
and
Server position calculations for all ships -> Ship position is set externally -> Render locally
Player's game is stored in local file or remote server for future ghost/reference
Player -> Keyboard Input -> Ship calculations locally -> Store to file / server
As you can see, if you want to cover at least two of these modes, you need to detach the following elements and combine them according to your needs:
Renderization (AShipModelZ): What we render is a representation of a ship (and other elements), whose position, rotation and scale is known to us, but not how or where they have been calculated. They can come from:
- Remotely calculated ship (ShipModelZGhost): This element takes a stream of data from a server (mode 2 and 3) or a file (mode 0) to know what's the position of the ship every frame.
- Locally calculated ship (ShipModelZPlayer): This element calculates following position, rotation and scale from previous + input. Input can be:
- Local input (keyboard, wiifit, joystick...) (InputManager -> KeyboardGenerator -> ShipModelZControlProxy)
- Remote input (mode 1) (RemoteInputCommandGenerator ->ShipModelZControlProxy)
Input will deserve another post itself, as it can be more complicated than it seems :)
In the diagram below, green classes belongs to the api, no real meaningful functionality on them, in yellow, ShipModelZ implementation of them:
viernes, 28 de agosto de 2015
Multiplayer modes in CaboTrafalgar
Multiplayer modes in CaboTrafalgar
Commands and Generators
Some common vocabulary needed for understanding following text.Commands are the smaller representation of an action that a user can do, and it´s declared and generated by each Ship individually. Some ships will offer "rudder left", "right", "mainsail in", "out", while other can offer the same four plus "move weight right", "left". Commands are, therefore, up to the Ship creator and this game takes them and tries to find who can implement them.
Generators come into play then, once we have some commands, we ask the Generator Manager (or similar) how many generators we have available for each one.
There are some generic Generators like Keyboard, able to map all Commands, also Remote is able to map everything, but we might find other Generators restricted about the type of commands able to deal with.
Once we have Commands and the possible Generators, it´s up to the user map each command with the desired generator (they could be keyboard, remote, and also wiifit, joystick, or mouse, just as example).
Finally, each Command is mapped to the chosen generator through a finest specification, in case of keyboard, associating a key.
Multiplayer Mode 0, Offline competition (DONE)
This was the first multiplayer mode done in CaboTrafalgar, for which I have "recordserver". It stores all verified user´s games and best are shared so other users can see, learn and compete against them.It offers a ranking with times and users to enhance competition.
Information uploaded comprises position, rotation and commands executed per frame, this would be later (not yet) validated by recordserver, who shares back positions per frame, but erases commands.
This way, another player can´t simply take other person´s record as his, commands are needed and game reexecuted to validate authenticity (not yet again).
In order to make this mode, I needed to introduce a Ghost class able to reposition a Ship every frame, which gave me the idea of the current class atlas (to be explained in other post).
Multiplayer Mode 1, Shared controls on a local game (DONE)
We now offer the option of sharing an arbitrary number of Commands to another player, but all calculations are still made locally (your computer, not any of CaboTrafalgar servers) and the rendering is also local to your sole screen. This is, you must be sharing with somebody in your room.The transfer is made through a barcode able to be read by any SmartPhone that can open a web application with those shared controls on it (this uses our servers), or you could use our app for that (in progress).
As a result, commands travel from your guest´s device to our servers, only to be transfered back with no alteration to your computer, where they´re interpreted just like your own input. As much this is a local collaboration that these results would be uploadable to RecordServer as they were yours alone.
Multiplayer Mode 2, Multiplayer game
Little explanation here, who doesn´t know what´s this about? Game doesn´t happen in your computer anymore, you´re just rendering the movements our server will tell you, like they were Mode0 ghosts, and your input will travel far from your computer like it was a Mode1 SmartPhone.Of course, not everything is always so simple, and some calculation could be done at your computer for fluidity´s and optimization´s sake, but the principles are the same, we need a server online where the calculations are done, and new speeds and positions are redistributed for clients to render.
Multiplayer Mode 3, Shared multiplayer game
This is the goal I aim from the first day, this game was always defined as a collaborative multiplayer naval battle simulator. A captain that delegates functions in other players inside the ship, and with help of other ships, fights another number of players collaborating in other ships.Game again happens remotely, but controls can be shared remotelly as roles, or locally as controls, as a mix of Mode 1 and 2.
miércoles, 8 de julio de 2015
Nifty-flow code released
I finally managed to separate my nifty-flow from cabotrafalgar, and it's started to be ready to open to other users.
What's nifty-flow?
Code and example here: https://github.com/albertonavarro/nifty-flow
Feedback is welcome, the project works for me though
http://hub.jmonkeyengine.org/t/nifty-meets-flow/33139
What's nifty-flow?
- A library that helps you to create series of screens to be played in order.
- A library that helps you to share screens, as they won't need to know where to point next, screens are no longer pointing to other screens.
- A library that helps you to reuse the same screen, accesible from different screens, allowing decoupling content and routing.
- A library that (will) help you to apply template technologies to save time creating dynamic screens avoiding java builders.
- If you're using Spring or Guice for wiring your instances, we won't complain, it's generic enough to accept instances from any system (as long as you build the bridge )
Code and example here: https://github.com/albertonavarro/nifty-flow
Feedback is welcome, the project works for me though
http://hub.jmonkeyengine.org/t/nifty-meets-flow/33139
domingo, 3 de mayo de 2015
Understanding CaboTrafalgar Physics 1 - Tidal forces in shipModelZ
Index:
(To come)
This is the first entry of a series to explain where we are now with physics in the game, and where we want to be.
Most of the entries will refer this "dinghy", ShipModelZ, in regards of movement and commands.
This post is actually an easy win, and that's the best way to start a path, with a victory in an apparently difficult topic as "Tidal movement".
During the hundredths of seconds a frame lasts, several calculations are made to try to simulate movement realistically, considering real or apparent wind, boat position, sail position, rudder position and weight location. In this calculation, tide is not considered, as we're calculating movement OVER the tide (same way, we don't consider the movement of the earth around the Sun for highway speed calculation).
Once we know what's the tri-dimensional distance we want to move over the sea, we need to add the absolute movement of the water, giving as the final distance we have moved.
Code explanation:
private void updatePosition(float tpf) {
Vector3f shipOrientation3f = this.getGlobalDirection();
this.move(shipOrientation3f.x * localSpeed * tpf, 0, shipOrientation3f.z * localSpeed * tpf);
this.move(getContext().getWater().getMovement(this.getGlobalDirection()).mult(-30 * tpf));
shipDirection.setValue(shipOrientation3f);
}
For this model, all movement is calculated in the direction of the keel, no lateral movement has been modelled yet. Direction of the keel is "this.getGlobalDirection()"
Local speed is the speed "ahead", direction defined by the keel, it acts as the modulus of a vector, while the keel is the direction of the vector.
As we're just interested in horizontal movement, we take first and third elements in the vector, times the speed, times tpf (time per frame) to get the final distance we've moved over the sea.
On top of this, we apply the movement of the tide for that particular location (this is a bug, we should be passing position, not orientation, but it doesn't matter, the implementation of water context is not as complex yet for it to matter), times some handy constant times time per framework.
So magic is done, on top of the ship movement, we have tide speed complicating everything.
That's it for today.. :)
(To come)
This is the first entry of a series to explain where we are now with physics in the game, and where we want to be.
Most of the entries will refer this "dinghy", ShipModelZ, in regards of movement and commands.
This post is actually an easy win, and that's the best way to start a path, with a victory in an apparently difficult topic as "Tidal movement".
During the hundredths of seconds a frame lasts, several calculations are made to try to simulate movement realistically, considering real or apparent wind, boat position, sail position, rudder position and weight location. In this calculation, tide is not considered, as we're calculating movement OVER the tide (same way, we don't consider the movement of the earth around the Sun for highway speed calculation).
Once we know what's the tri-dimensional distance we want to move over the sea, we need to add the absolute movement of the water, giving as the final distance we have moved.
Code explanation:
private void updatePosition(float tpf) {
Vector3f shipOrientation3f = this.getGlobalDirection();
this.move(shipOrientation3f.x * localSpeed * tpf, 0, shipOrientation3f.z * localSpeed * tpf);
this.move(getContext().getWater().getMovement(this.getGlobalDirection()).mult(-30 * tpf));
shipDirection.setValue(shipOrientation3f);
}
For this model, all movement is calculated in the direction of the keel, no lateral movement has been modelled yet. Direction of the keel is "this.getGlobalDirection()"
Local speed is the speed "ahead", direction defined by the keel, it acts as the modulus of a vector, while the keel is the direction of the vector.
As we're just interested in horizontal movement, we take first and third elements in the vector, times the speed, times tpf (time per frame) to get the final distance we've moved over the sea.
On top of this, we apply the movement of the tide for that particular location (this is a bug, we should be passing position, not orientation, but it doesn't matter, the implementation of water context is not as complex yet for it to matter), times some handy constant times time per framework.
So magic is done, on top of the ship movement, we have tide speed complicating everything.
That's it for today.. :)
Suscribirse a:
Entradas (Atom)