Jump to content

[1.15.2] Questions regarding dynamically-generated GUIs


Blazer Nitrox

Recommended Posts

Okay, so admittedly this is a really complex thing I'm trying to do, but I figure I'd learn some things along the way, so I decided to give it a shot.

My goal is to recreate something like a skill tree in Minecraft. Specifically, my inspiration comes from the Passive Skill Tree in Path of Exile. I definitely do NOT expect to replicate the kind of complexity they've got, as the skill tree has evolved over the course of many years.
I've (mostly) figured out my implementation for the skill tree, and now I need to have a GUI allowing the player to actually allocate their skill points. This, unfortunately, seems to be much harder than the tree implementation itself, while also being extremely important to using the tree (I suppose I could implement the whole thing using commands, but I'm going to have to create a GUI at some point so I might as well ask now).

Currently, I'm trying to future-proof by using a custom registry for my SkillNodes. While this will allow me to easily add nodes programmatically (and will allow other mods to expand upon the skill tree), it also means that I have no way of knowing what nodes will need to be drawn to the tree at compile time, so I have to dynamically generate the GUI. I likewise have no way of knowing what positions I will need to draw the nodes' textures at, which means I won't know until runtime how long I will need to make the paths between each node.


So, here are my questions:
1.  I expect I will need to take advantage of Minecraft's built-in texture stitching, that way I don't have to rebind the texture every time I draw a node. Near as I can tell, there is no built-in GUI TextureAtlas, so I would need to create my own. How would I go about doing that, and would it need to be registered somewhere in order to still fire TextureStitchEvents?
2a.  Since I also don't know each node's position until runtime, I will need to dynamically draw the paths between them, which means I won't know the lengths of each path and therefore can't just draw a texture 1:1 between each node.

2b. I would like to make the paths straight lines between each node, which means I could potentially be dealing with diagonals as well. Can  `Screen` handle either of these scenarios, or would I need to directly access OpenGL?

 

This situation is something I'm not very experienced with at all, so please bear with me. I'll probably be asking several more questions to make sure I understand what I need to do here.

And as I said, I know I'm probably biting off more than I can chew, but I find that I learn best by bodging a solution together at first and then rewriting it with the knowledge I gained.

 

 

Link to comment
Share on other sites

43 minutes ago, Blazer Nitrox said:

While this will allow me to easily add nodes programmatically (and will allow other mods to expand upon the skill tree), it also means that I have no way of knowing what nodes will need to be drawn to the tree at compile time, so I have to dynamically generate the GUI. I likewise have no way of knowing what positions I will need to draw the nodes' textures at, which means I won't know until runtime how long I will need to make the paths between each node.

You could specify the position of the node as part of its data, which should be part of the instance you use as the registry entry, and draw each node according to its position data. This allows you (as well as other mods that use your mod) to have more control over where the newly added nodes should appear. This also circumvents the "dynamically position the node and draw line between" problem.

 

43 minutes ago, Blazer Nitrox said:

2a.  Since I also don't know each node's position until runtime, I will need to dynamically draw the paths between them, which means I won't know the lengths of each path and therefore can't just draw a texture 1:1 between each node.

2b. I would like to make the paths straight lines between each node, which means I could potentially be dealing with diagonals as well. Can  `Screen` handle either of these scenarios, or would I need to directly access OpenGL?

Directly using OpenGL should be the easier approach, as it has more flexibility (and Screen uses OpenGL anyways). Create a texture of a line and scale/rotate it to point it to other nodes. Some trigonometry will be needed.

Edited by DavidM

Some tips:

Spoiler

Modder Support:

Spoiler

1. Do not follow tutorials on YouTube, especially TechnoVision (previously called Loremaster) and HarryTalks, due to their promotion of bad practice and usage of outdated code.

2. Always post your code.

3. Never copy and paste code. You won't learn anything from doing that.

4. 

Quote

Programming via Eclipse's hotfixes will get you nowhere

5. Learn to use your IDE, especially the debugger.

6.

Quote

The "picture that's worth 1000 words" only works if there's an obvious problem or a freehand red circle around it.

Support & Bug Reports:

Spoiler

1. Read the EAQ before asking for help. Remember to provide the appropriate log(s).

2. Versions below 1.11 are no longer supported due to their age. Update to a modern version of Minecraft to receive support.

 

 

Link to comment
Share on other sites

29 minutes ago, DavidM said:

You could specify the position of the node as part of its data, which should be part of the instance you use as the registry entry, and draw each node according to its position data. This allows you (as well as other mods that use your mod) to have more control over where the newly added nodes should appear. This also circumvents the "dynamically position the node and draw line between" problem.

That's what I'm doing, sorry I didn't make that clear. As part of the node's data I've got the position and the resource location for the appropriate texture. My main concern was that since I won't know where the node will be located until it gets registered I can't just include the path as part of the background texture, so I would have to dynamically draw it.

 

32 minutes ago, DavidM said:

Directly using OpenGL should be the easier approach, as it has more flexibility (and Screen uses OpenGL anyways). Create a texture of a line and scale/rotate it to point it to other nodes. Some trigonometry will be needed.

I suspected this would be the case, but thanks for confirming it. I assume I can still use Screen for a convenient way to access the GUI, and then override the various rendering methods in order to actually draw what I need?

Side note: how did you code-ify `Screen`?

Link to comment
Share on other sites

Okay, I did some poking around with AtlasTexture, and I think I figured out how to do what I need to. I'm going to record it here for future generations, and as a second post because it just doesn't make sense to me to combine this with the above, since they're mostly unrelated (and the separation will help identify this as me explaining what I think I need to do).

 

AtlasTexture texture = new AtlasTexture(new ResourceLocation(MODID, "atlas"));
Stream<ResourceLocation> stream = list.stream(); //We get a stream of all of the textures we want, probably using Collection#stream()
SheetData data = texture.func_229220_a_((IResourceManager) Minecraft.getInstance().getTextureManager(), stream, Minecraft.getInstance().getProfiler(), 0); //Last argument is mipmap level, we use 0
map.upload(data);

So, first I must actually create a new AtlasTexture, passing in a ResourceLocation which will act as a registry name.
Then, to actually generate the stitched texture, I must call AtlasTexture#func229220_a_. It seems I should pass in Minecraft.getInstance().getTextureManager() (which I can do because I'm specifically doing this on Dist.CLIENT) for the first argument, a Stream containing the ResourceLocation's of all of the textures I want to stitch (so this will be done after all of the nodes are registered, no surprise there), an IProfiler (whatever that is, it seems to be used to track the progress of the texture stitching), and an integer mipmapping level (I would assume I'd use either 0 or 1, since I won't need mipmapping for a GUI I don't think). I would capture the output of this and pass it to AtlasTexture#upload, which would actually create the texture information I would then use for drawing. At this point, the AtlasTexture is ready for use.

 

if (!RenderSystem.isOnRenderThread()) {
	RenderSystem.recordRenderCall(() -> {
		manager.func_229263_a(texture.func_229223_g_(), texture);
      	texture.func_229148_d_();
	});
} else {
	textureManager.func_229263_a(texture.func_229223_g_(), texture);
  	texture.func_229148_d();
}

When we first start drawing, we have to bind the texture. First, we must ensure we are actually on the rendering thread, and ask the rendering thread to do it if we aren't. Then we call TextureManager#func_229263_a, which takes two arguments, a ResourceLocation we want to use as a key for our AtlasTexture and the AtlasTexture itself.  We can cheat the first argument here by calling AtlasTexture#func_229223_g_, which is essentially getRegistryName. This will set up our texture so it's ready to be bound to OpenGL (the above is ripped directly from TextureManager#bindTexture). We then call Texture#func229148_d, which actually binds the texture.

 

((I would provide a code example, except that Screen doesn't seem to provide any way to draw textures using UV coordinates which is all TextureAtlasSprite provides, and directly drawing via OpenGL requires calling Tessellator to get a BufferBuilder, and none of the methods in BufferBuilder (or, for that matter, Screen) have been deobfuscated yet.))

 

Now, in order to draw a texture from the Atlas onto the GUI, we call AtlasTexture#getSprite and pass in the key for the particular texture we want. That returns a TextureAtlasSprite, which contains the coordinates of our texture on the Atlas in absolute pixels as well as the relative min/max UV coordinates of our texture. Asking OpenGL to draw the part of the texture from min UV to max UV will draw our chosen texture and only that texture.

Link to comment
Share on other sites

Few things to add:

1. You might want to check out SpriteUploader.

2. Check out AbstractGui::blit. It is the built-in method of rendering sprites with UV coords.

3. You won't need to check on for the rendering thread if you are rendering in the Screen code (in the appropriate method).

 

On an irrelevant note, almost everything in BufferBuilder as well as AtlasTexture (and a few other mentioned classes) has been deobfuscated on recent MCP mappings. You should consider updating your mappings if you find them obfuscated on your setup.

  • Like 1
  • Thanks 1

Some tips:

Spoiler

Modder Support:

Spoiler

1. Do not follow tutorials on YouTube, especially TechnoVision (previously called Loremaster) and HarryTalks, due to their promotion of bad practice and usage of outdated code.

2. Always post your code.

3. Never copy and paste code. You won't learn anything from doing that.

4. 

Quote

Programming via Eclipse's hotfixes will get you nowhere

5. Learn to use your IDE, especially the debugger.

6.

Quote

The "picture that's worth 1000 words" only works if there's an obvious problem or a freehand red circle around it.

Support & Bug Reports:

Spoiler

1. Read the EAQ before asking for help. Remember to provide the appropriate log(s).

2. Versions below 1.11 are no longer supported due to their age. Update to a modern version of Minecraft to receive support.

 

 

Link to comment
Share on other sites

9 hours ago, DavidM said:

Few things to add:

1. You might want to check out SpriteUploader.

2. Check out AbstractGui::blit. It is the built-in method of rendering sprites with UV coords.

3. You won't need to check on for the rendering thread if you are rendering in the Screen code (in the appropriate method).

 

On an irrelevant note, almost everything in BufferBuilder as well as AtlasTexture (and a few other mentioned classes) has been deobfuscated on recent MCP mappings. You should consider updating your mappings if you find them obfuscated on your setup.

Oh, hey! In the process of updating my MCP mappings I noticed that for some reason Forge was claiming it was 1.15 (and downloading Minecraft 1.15), but it was using the 1.14 MCP mappings, so I had no idea that AbstractGui#blit had a version which took a TextureAtlasSprite.

 

So SpriteUploader seems to do exactly what I was originally expecting, with the added bonus of being able to be called whenever the resource engine reloads. That is very useful, although SpriteUploader#getSprite is set to protected, limiting its use pretty drasically IMHO. I assume the idea is that I would ask it to bind the texture atlas and then access the atlas directly from there.

 

As far as checking the rendering thread, I figured that the relevant methods would always be called on the rendering thread, but since bindTexture does it I figured it wouldn't hurt.

 

All of this has been extremely helpful, thanks a bunch!

Link to comment
Share on other sites

Might also be worth looking at how vanilla handles the Advancements screen.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Link to comment
Share on other sites

Okay, having taken a bit to mess around with everything, I think I understand how I need to go about this. I did notice that SpriteUploader doesn't seem to have any markers for being called on reload, is there some way I should be registering it with the resource manager, or does it take care of that itself somewhere that I'm not seeing? There certainly doesn't appear to be any events I can attach to for registration or to manually call reload(), nor does TextureManager provide any such functionality.

Edited by Blazer Nitrox
Minor correction in wording
Link to comment
Share on other sites

What stops you from knowing a node's position at runtime? Try to separate the concept of a node to anything related to your skill tree.

 

If you can calculate/determine what skills a player knows at any time from player capabilities, access it from the GUI. Determine a pre-determined layout of the tree, like where a node will go (the physical node, not the skills attached to it) and where to draw arrows pointing to it. For example, if you have 40 skills in the tree, you could set up a lookup array of size 40 to determine if a node representing a particular skill should be drawn or not. This is the background layer. And in the foreground, overlay your skill name, description, etc over their respective positions. Then within the various mouse action methods, access this array whether it should detect player hovers/clicks there or not.

 

This is something similar I've done, if a player doesn't want to have the third row for example, the node and the arrows pointing to it aren't drawn. Notice that some concessions needed to be made, like the length of the names (I've formatted the string so it's inside the node, but anything longer will make the text go over the boundary). But if I wanted to, I could even vary the size of each node and have a lookup table that tells me how large a certain node needs to be for a certain skill name.

 

Edit: All a node should be is a quad with a texture, nothing more. You shouldn't need to register anything as David said

 

image.png.0ed0a18e21ed9ac582756a83bf9dbe71.png

Edited by Turtledove
Link to comment
Share on other sites

6 minutes ago, Turtledove said:

What stops you from knowing a node's position at runtime? Try to separate the concept of a node to anything related to your skill tree.

 

If you can calculate/determine what skills a player knows at any time from player capabilities, access it from the GUI. Determine a pre-determined layout of the tree, like where a node will go (the physical node, not the skills attached to it) and where to draw arrows pointing to it. For example, if you have 40 skills in the tree, you could set up a lookup array of size 40 to determine if a node representing a particular skill should be drawn or not. This is the background layer. And in the foreground, overlay your skill name, description, etc over their respective positions. Then within the various mouse action methods, access this array whether it should detect player hovers/clicks there or not.

 

This is something similar I've done, if a player doesn't want to have the third row for example, the node and the arrows pointing to it aren't drawn. Notice that some concessions needed to be made, like the length of the names (I've formatted the string so it's inside the node, but anything longer will make the text go over the boundary). But if I wanted to, I could even vary the size of each node and have a lookup table that tells me how large a certain node needs to be for a certain skill name.

 

image.png.0ed0a18e21ed9ac582756a83bf9dbe71.png

Under normal circumstances I would have done it that way, however I know that over time I'm going to be adding nodes to the tree (and potentially letting other mods add nodes to the tree as well), so I want to be able to programmatically add the nodes to the tree, meaning I won't know their textures or positions until they are registered. While it does mean I have to do more work in building the screen, theoretically it will also allow me to simply register a new node without having to modify the tree itself. It would also potentially allow me to create a tool later on which allows me to visually build the tree out and then simply export it as a JSON which the mod could then load, although it'll be a looong time before I pursue that route.

Link to comment
Share on other sites

5 minutes ago, Blazer Nitrox said:

so I want to be able to programmatically add the nodes to the tree, meaning I won't know their textures or positions until they are registered

Again, look at the advancements screen. It positions every advancement dynamically based on the advancements found during resource loading and they never overlap or have lines cross.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Link to comment
Share on other sites

43 minutes ago, Turtledove said:

Edit: All a node should be is a quad with a texture, nothing more. You shouldn't need to register anything as David said

OP wants to use the registry system for skill tree nodes so that he can manage the skill categories easier later on as well as allow other mods to add theirs. The nodes only manages how the tree is displayed; OP uses the registry system for the skill categories, not the actual icon (which can be stored in the registry entry for simplicity given that there is a one-to-one mapping for all of the categories).

 

 

Some tips:

Spoiler

Modder Support:

Spoiler

1. Do not follow tutorials on YouTube, especially TechnoVision (previously called Loremaster) and HarryTalks, due to their promotion of bad practice and usage of outdated code.

2. Always post your code.

3. Never copy and paste code. You won't learn anything from doing that.

4. 

Quote

Programming via Eclipse's hotfixes will get you nowhere

5. Learn to use your IDE, especially the debugger.

6.

Quote

The "picture that's worth 1000 words" only works if there's an obvious problem or a freehand red circle around it.

Support & Bug Reports:

Spoiler

1. Read the EAQ before asking for help. Remember to provide the appropriate log(s).

2. Versions below 1.11 are no longer supported due to their age. Update to a modern version of Minecraft to receive support.

 

 

Link to comment
Share on other sites

@Draco18s I took another look at the advancements screen and realized I was a complete moron lol. I had been avoiding doing a proper parent/child hierarchy because I want the player to be able to loop around the tree like so:

image.png.ac79200ae962b137e563f795d48e9f31.png

(excuse the terrible quality, I threw this together in like 2 minutes in Paint because I didn't feel like waiting for GIMP to load)

At the time, I had thought that a parent/child hierarchy wouldn't be capable of this because the last node in the sequence (top right) wouldn't have it's parent node active, and so it would think that it couldn't be activated. And I only just now realized that when the player tries to activate the node I could just... check if any child nodes are activated as well.
 

All of this is to say that I've been a miserable fool who wasted his time because his solution was only half-baked. Nice.

Edited by Blazer Nitrox
Link to comment
Share on other sites

34 minutes ago, Blazer Nitrox said:

I had thought that a parent/child hierarchy wouldn't be capable of this because the last node in the sequence (top right) wouldn't have it's parent node active, and so it would think that it couldn't be activated. And I only just now realized that when the player tries to activate the node I could just... check if any child nodes are activated as well.

If your tree is not a tree (as in data structure; in your case it resembles a graph), then there is no need to separate the parent and child. Instead of keeping track of parent and children, create a set for all other connect nodes (given that direction does not matter in your skill graph).

Edited by DavidM

Some tips:

Spoiler

Modder Support:

Spoiler

1. Do not follow tutorials on YouTube, especially TechnoVision (previously called Loremaster) and HarryTalks, due to their promotion of bad practice and usage of outdated code.

2. Always post your code.

3. Never copy and paste code. You won't learn anything from doing that.

4. 

Quote

Programming via Eclipse's hotfixes will get you nowhere

5. Learn to use your IDE, especially the debugger.

6.

Quote

The "picture that's worth 1000 words" only works if there's an obvious problem or a freehand red circle around it.

Support & Bug Reports:

Spoiler

1. Read the EAQ before asking for help. Remember to provide the appropriate log(s).

2. Versions below 1.11 are no longer supported due to their age. Update to a modern version of Minecraft to receive support.

 

 

Link to comment
Share on other sites

2 minutes ago, DavidM said:

If your tree is not a tree (as in data structure; in your case it resembles a graph), then there is no need to separate the parent and child. Instead of keeping track of parent and children, create a set for all other connect nodes.

Right, and that's how I've been doing it. I think you actually might have touched on the underlying problem... data structures have always kind of confused me for some reason, and I think part of the problem is in some ways I'm still thinking of this as a tree when in reality what I need is a graph, just with a pseudo-root node.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.