Scene2D, utter tedious

Is it just me or is messing with Scene2D tedious as hell.

I have been coding a Space Invaders game for the past couple of days, using LibGDX and I have the game almost complete (death animation missing) so I decided to add a menu.

Now it would have 2 buttons, play and quit. Jesus, the amount of fields/variables required to just do that was horrible.

Then the skin, god annoying.

Don’t get me wrong, I have used it to good effect in the past but it just feels like a tedious and ugly library when it comes to coding with it.

Anyone feel the same or just me?

I ended up just creating a simple UIHandler, a few buttons, a list to holds the buttons and a way to navigate them with the keys.

It took around 5-6 classes but all I have to do is this:



UIHandler uiHandler = new UIHandler();

	TextButton play = new TextButton("PLAY", Gdx.graphics.getWidth() / 2,
			Gdx.graphics.getHeight() / 2, 300, 100, new ButtonCallback() {
				
				@Override
				public void onClick() {
					((Game)Gdx.app.getApplicationListener()).setScreen(new GameScreen());
				}
			});
	
	TextButton exit = new TextButton("QUIT", Gdx.graphics.getWidth() / 2,
			Gdx.graphics.getHeight() / 2, 300, 100, new ButtonCallback() {
				
				@Override
				public void onClick() {
					Gdx.app.exit();
				}
			});

		list.addWidget(play);
		list.addWidget(exit);
		uiHandler.addWidget(list);


You can see where the similarities lie, the ui hander is like the Stage and the list is like the List in Scene2D, this was pretty fun to write, it might not look pretty but at least I am not fighting with a whole bunch of exceptions to get Scene2D to agree :stuck_out_tongue:

Can you perhaps show the rest of the code? It usually doesn’t take me more than a few lines to create a basic menu.

The rest of my code? That is pretty much it in terms of creating the menu, do you mean my actual back classes? If you mean Scene2D, I have no Scene2D code.

It is very similar to Scene2D except I don’t have a skin crashing 6x before everything is put into it.

I don’t have any of the problems with Scene2D (like exceptions).
IIRC there is an “old” skin file around and if you try to use that you’ll likely get errors, because Scene2D received an update for the SelectBox or something.

Once you’re using a current version of the default skin you’re good to go.

Also you don’t really need to use the skin if all you’re doing is creating two buttons. The setup is quite slim without a skin too.

I feel your pain… when I used LibGDX, Scene2D was the one part I always feared when creating games. Its just so bloated and unnecessary when creating a simple menu that consists of a few buttons. I ended up just writing my own GUI code which worked perfectly fine.

If you want to make a complex GUI with sliders, editable text areas, checkboxes and other stuff then homebrewing stuff just does not cut it and becomes a big waste of time. In my experience Scene2D UI is quite elegant and requires few lines of code to get going.

Also, I cant really see the complexity in Scene2D ui, it’s actually very simple and is just a couple of lines more than you are using. Yeah setting up a skin is some work, but actually a lot easier than in for example TWL IMHO.

:scratches-head: are we talking about the 23 lines of code in the OP because basic Swing GUIs I’ve worked have required 400-600 lines of code just to get a few buttons and text boxes aligned and properly resizing.
You can always make a utility class to create basic menus for you if your menus always consist of a few buttons in the same positions.

@teamworkbuy

Agreed… I don’t see how OP’s code is messy. Yes it could use .addCallBack() so you don’t have to add call back in the constructor, but other than that, the code seems rather clean.

Here is what you would start calling messy. Adding few buttons without functions is one thing. Its other thing to make everything work together. Here is a really small project I worked on for couple of days. It had like 5 buttons and 1 JList.


frame = new JFrame("Leauge Of Legends Simple Bot");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setResizable(false);

		JPanel leftPanel = new JPanel(new GridBagLayout());
		GridBagConstraints lc = new GridBagConstraints();
		lc.insets.top = 2;
		lc.insets.bottom = 2;
		lc.gridy = 0;
		frame.add(leftPanel, BorderLayout.WEST);

		JPanel rightPanel = new JPanel(new GridBagLayout());
		frame.add(rightPanel, BorderLayout.EAST);
		GridBagConstraints rc = new GridBagConstraints();
		rc.insets.top = 2;
		rc.insets.bottom = 2;
		rc.gridy = 0;

		buttonImport = new JButton("Import configuration");
		buttonImport.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				int val = fileChooser.showOpenDialog(frame);

				if (val == JFileChooser.APPROVE_OPTION) {
					File file = fileChooser.getSelectedFile();

					if (file.exists()) {

						String name = file.getName();
						String[] names = name.split("\\.");

						if (names.length == 2 && names[1].equals("botconfig")) {

							try {
								ParserDevice device = new ParserDevice(new FileReader(file));

								try {
									final BotConfigData data = new BotConfigData(file);
									data.load(device);

									dataList.add(data);

									SwingUtilities.invokeLater(new Runnable() {
										public void run() {
											jlistmodel.addElement(data);
										}
									});
								} catch (NullPointerException e) {
									showError("Cannot parse file. It may be corrupt.");
								}
							} catch (IOException e) {
								e.printStackTrace();
							}

						} else {
							showError("Wrong file format.\nExpected format is .botconfig");
						}

					} else {
						showError("File doesn't exist.");
					}
				}
			}
		});
		leftPanel.add(buttonImport, lc);

		jlistmodel = new DefaultListModel<BotConfigData>();
		jlist = new JList<BotConfigData>(jlistmodel);
		jlist.addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent arg0) {
				if (jlist.getSelectedIndex() == -1) {
					currentConfigData = null;
					buttonStart.setEnabled(false);
					buttonRemove.setEnabled(false);
					return;
				}
				currentConfigData = dataList.get(jlist.getSelectedIndex());
				buttonStart.setEnabled(true);
				buttonRemove.setEnabled(true);
			}
		});

		JScrollPane scroller = new JScrollPane(jlist);
		scroller.setPreferredSize(new Dimension(250, 100));
		rightPanel.add(scroller, rc);

		lc.gridy++;
		buttonRemove = new JButton("Remove configuration");
		buttonRemove.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
			
				BotConfigData data = currentConfigData;
				int val = JOptionPane.showConfirmDialog(frame, "Are you sure you want to remove: "+data.toString()+" ?", "Confirm your selection", JOptionPane.YES_NO_OPTION);
				
				if(val==JOptionPane.OK_OPTION) {
					final int index = dataList.indexOf(currentConfigData);
					
					dataList.remove(index);
					
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							jlistmodel.removeElementAt(index);
						}
					});
					
					JOptionPane.showMessageDialog(frame, "Configuration "+data.toString()+" has been removed.");
				}
			}
		});
		buttonRemove.setEnabled(false);
		leftPanel.add(buttonRemove, lc);

		lc.gridy++;
		buttonStart = new JButton("Start bot");
		buttonStart.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						buttonImport.setEnabled(false);
						buttonRemove.setEnabled(false);
						jlist.setEnabled(false);
						buttonStart.setEnabled(false);
						buttonStop.setEnabled(true);
					}
				});
				
				new Thread() {
					public void run() {
						Bot.instance.botLogic.start(dataList.get(jlist.getSelectedIndex()));
					}
				}.start();
			}
		});
		buttonStart.setEnabled(false);
		leftPanel.add(buttonStart, lc);

		lc.gridy++;
		buttonStop = new JButton("Stop bot");
		buttonStop.setEnabled(false);
		buttonStop.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				stop();
				Bot.instance.botLogic.stop();
			}
		});
		leftPanel.add(buttonStop, lc);

		frame.pack();
		frame.setLocationRelativeTo(null);
		frame.setVisible(true);

		frame.addWindowListener(getClosingWindowListener());

And this is not all the code that has to do something with GUI…

This is what I call messy.

Some people probably find my code clean. It probably depends on your coding skill level.

The 23 lines of code in the OP is my code, not Scene2D.

If it was scene2d the lines would be doubled, to get the same thing.

Scene2D is great when it comes to a semi complex UI, but see trying to get something basic to work…annoying as shit imo.

I have run into many issues with scene2d’s ui stuff. It is sad that I have to say to stay away from it. There are focus issues, rendering issues where things get cutoff oddly. I have tried multiple version of libgdx but they all have the same issues.

Personally I’ve never had any problems with it, obviously you are always going to sacrifice simplicity for power. A menu with two buttons is pretty trivial:


TextButton startButton = new TextButton("Start", getSkin());
		startButton.addListener(new ClickListener(){
			@Override
			public void clicked(InputEvent event, float x, float y){
				//blah
			}
		});
TextButton quitButton = new TextButton("Start", getSkin());
		quitButton.addListener(new ClickListener(){
			@Override
			public void clicked(InputEvent event, float x, float y){
				//blah
			}
		});
table.add(startButton);
table.row();
table.add(quitButton);

right?
About the same amount of code as you have, except you don’t have to manually resize each element.

My issues is not that writing stuff is hard, but that it doesn’t work right.

I was referring to the OP, but what are the specific problems?

Except you are missing the creation and setup of tgr stage, the table, sizing both.

Them setting up the skin so that it has a font and drawables.

I don’t have to manually resize my widgets, I added in 1 line if code that sizes it to the parent if you wish.

I dunno, I just can’t seem to do well with it and the thought of it puts me right off.


skin = new Skin(Gdx.files.internal("skin/uiskin.json"));
Table table = new Table(skin);
stage.addActor(table);			

Tedious?
Also scene2d actually uses less than one line to resize elements:


table.add(button).fill();

But you can also set align, borders, manually set w/h, etc too. I think it’s just the way of thought that’s different, instead of thinking about each button and where it goes you think about the table as a whole.

Yeah for me scene2D.ui has been gloriously concise.
Now I should make a drag and drop ui builder for it…

That’d actually be really good, show the cell boundaries and stuff…like the old table layout editor with descriptors

You mean like the debug mode? (Just with more info?)

Yeah basically, easiest way to determine why some component isn’t going where you want it to.

In the hypothetical editor: right-click component in debug mode to bring up a pop-up stacktrace of alignment functions taking place on it.
Could be interesting, might actually do it. Not now though, the rest of my spring break is devoted to this damn CPU emulator I’m writing.