Skip to content

Instantly share code, notes, and snippets.

@miketamis
Forked from fkaa/gist:3312473
Created August 10, 2012 08:11
Show Gist options
  • Save miketamis/3312532 to your computer and use it in GitHub Desktop.
Save miketamis/3312532 to your computer and use it in GitHub Desktop.

Basic GUI

Saspiron's basic GUI will normally contain a series of buttons, and a background/image. To demonstrate those features, this GUI will render in-game model specified in the constructor. Starting with the class, we'll want to extend GuiScreen and declare what package we're in:

package net.minecraft.src;

public class GuiBasic extends GuiScreen {
    
}

Next, the GUI's constructor, you only want to pass arguments which will be used later in the GUI. Since we're going to render in-game models, we'd create a constructor looking something like this, which will take a ModelBase, a String for the texture, and assign it to global variables.

package net.minecraft.src;

public class GuiBasic extends GuiScreen {

    public ModelBase modelbase;
    public String texture
    
    public GuiBasic(ModelBase modelbase, String texture) {
        this.modelbase = modelbase;
        this.texture = texture;
    }

}

We'll also add two buttons, which will rotate the model on the x-axis. To do this, we must first add two buttons to the list controlList from the super-class GuiScreen. The GuiButton has two constructors, the first having the parameters: id, x, y, width, height, label. The other constructor being: id, x, y, label The id is used later on in the method actionPerformed in order to specify what the button will do when pressed, so choose a unique id for each button (You can also create your own button class with callback support to avoid having to use IDs). The other parameters are pretty self-explanatory.

For the positioning and diameters of the buttons, we want them to be a 20x20 box, centered at the bottom, with < and > showing what direction they will rotate the model. For this we'll use the height of the window and subtract it with 30, so that they're 10 pixels from the bottom. Then we take the width of the window and divide it by two, minus the buttons width divided by two and then offset that value by 50 in opposite directions.

Each time the window is resized, every button is removed in order to update it's position. So instead of placing them in the constructor, we have to overide a super-method from GuiScreen called initGui. This method is called each time the window is resized unlike the constructor, which is only called once.

package net.minecraft.src;

public class GuiBasic extends GuiScreen {

    public ModelBase modelbase;
    public String texture

    public GuiBasic(ModelBase modelbase, String texture) {
        this.modelbase = modelbase;
        this.texture = texture;
    }

    @Override
    public void initGui() {
        this.controlList.add(new GuiButton(0, width / 2 - 20 / 2 - 50, height - 30, 20, 20, "<"));
        this.controlList.add(new GuiButton(1, width / 2 - 20 / 2 + 50, height - 30, 20, 20, ">"));
    }

}

Now that we've added the buttons to the list, we have to specify what they're going to do when clicked. To do this we override the super-method actionPerformed from GuiScreen. This method is called each time a button from the list controlList is pressed and passes that GuiButton as an argument in the method.

    @Override
    public void actionPerformed(GuiButton button) {
        
    }

To find out what button was passed, we'll use the id, specified in the constructor. You can either use a switch statement:

    @Override
    public void actionPerformed(GuiButton button) {
        switch (button.id) {
            case 0:
                break;
            case 1:
                break;
        }    
    }

or a bunch of if's:

    @Override
    public void actionPerformed(GuiButton button) {
        if (button.id == 0) {

        }
        if (button.id == 1) {

        }
    }

Before we specify what to do, we have to add another global variable, which will store the current rotation on the x-axis.

    public float xAxis = 0;

To rotate the model, we're going to increment or decrement the variable xAxis each time depending on which button is pressed. Right now the button with the id 0 will make decrement the variable and the one with the id 1 will increment it.

    @Override
    public void actionPerformed(GuiButton button) {
        switch (button.id) {
            case 0:
                this.xAxis += 10;
                break;
            case 1:
                this.xAxis -= 10;
                break;
        }    
    }

Now our buttons are working perfectly, but the GUI's lacking in both a background and the model we're going to rotate. To draw onto the GUI, we have to override another super-method from GuiScreen called drawScreen which is called each frame. Again, the parameters are pretty much self-explanatory and we wont even be using them at all.

    @Override
    public void drawScreen(int mouseX, int mouseY, float tick) {
        
    }

If you look in super-method (GuiScreen.drawScreen), you'll see that it draws all the buttons with a for-loop. To do the same thing, all we need to do is call super.drawScreen(mouseX, mouseY, tick) which will then render the buttons in the current GUI. After doing that, we're going to add the default dirt background. To draw the dirt background, we're going to call a super-method which is called drawDefaultBackground. A thing to note about drawing while in orthographic mode (2D) is that the order in which the components are drawn is equal to the z-axis here. For example: if you first draw a picture of a pig, and after that, a picture of a cow, the cow will overlap the image of the pig, being closer on the z-axis. That's why we're going to call the dirt-drawing first, and the buttons last, so that they will always be on the top/front.

    @Override
    public void drawScreen(int mouseX, int mouseY, float tick) {
        this.drawDefaultBackground();
        super.drawScreen(mouseX, mouseY, tick);
    }

Now that we've got our background and our fancily placed buttons, it's time to start on the model rendering. To keep things tidy, we'll create our own new method for rendering the model, which will be called in drawScreen. For the method we'll take no arguments, as they are already specified in the constructor.

    @Override
    public void drawScreen(int mouseX, int mouseY, float tick) {
        this.drawDefaultBackground();
        this.drawModel();
        super.drawScreen(mouseX, mouseY, tick);
    }

    public void drawModel() {

    }

Now for the tricky part, rendering the actual model, which wont be delved into that much, apart from the comments in the code. It's basically just iterating over all the model's boxes and rendering them, plus a lot of OpenGL calls which turns on lighting, moves and rotates the model.

To do this you also need to import some OpenGL classes, and since we're only going to use static methods, we can use static imports.

	import static org.lwjgl.opengl.GL11.*;
	import static org.lwjgl.opengl.GL12.*;

If you're not familiar with static imports. They basically allow for you to call the imported class's static methods without needing to refer to the class. So instead of doing GL11.glPushMatrix(), you'll just need to do glPushMatrix();

    public void drawModel() {
        glPushMatrix(); // Push matrix
        RenderHelper.enableStandardItemLighting(); // Enable lighting
        glTranslatef(width / 2, height / 3, 0); // Move the model to the center
        glRotatef(-25, 1, 0, 0); // Rotate the model initially to get a better view
        glRotatef(xAxis, 0, 1, 0); // Rotate the model on the x-axis with the variable xAxis
        glPushMatrix(); // Push matrix
        glEnable(GL_DEPTH_TEST); // Enable depth-testing
        glEnable(GL_BLEND); // Enable blending
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Set blend func
        glDisable(GL_CULL_FACE); // Disable depth culling
        glEnable(GL_RESCALE_NORMAL);
        glBindTexture(GL_TEXTURE_2D, mc.renderEngine.getTexture(texture)); // Bind texture
        for (int i = 0; i < this.modelbase.boxList.size(); i++) {
            ((ModelRenderer) (this.modelbase.boxList.get(i))).render(4); // Iterate over the model's parts and render them
        }
        glDisable(GL_RESCALE_NORMAL);
        glEnable(GL_CULL_FACE); // Enable depth culling
        glDisable(GL_BLEND); // Disable blending
        glDisable(GL_DEPTH_TEST); // Disable depth-testing
        glPopMatrix(); // Pop matrix
        RenderHelper.disableStandardItemLighting(); // Disable lighting
        glPopMatrix(); // Pop matrix
    }

After you're done with that, the GUI should be working swimmingly and look something like this, by initializing the GUI like this mc.displayGuiScreen(new ModelCreeper(), "/mob/creeper.png")

Alt text

A few final touches you can add to the GUI would be something like adding a series of buttons which represent each in-game model and on clicked, changes the current model to it. Another thing which can be done to improve the usability of the GUI would be to store the model's class and path to its texture in enums. Finally, making the model spin by a small amount each frame really adds to the scene.

Final Code:

package net.minecraft.src;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.*;

public class GuiBasic extends GuiScreen {

    public ModelBase modelbase;
    public String texture;
    public float xAxis = 0;

    public GuiBasic(ModelBase modelbase, String texture) {
        this.modelbase = modelbase;
        this.texture = texture;
    }

    @Override
    public void initGui() {
        this.controlList.add(new GuiButton(0, width / 2 - 20 / 2 - 50, height - 30, 20, 20, "<"));
        this.controlList.add(new GuiButton(1, width / 2 - 20 / 2 + 50, height - 30, 20, 20, ">"));
    }

    @Override
    public void actionPerformed(GuiButton button) {
        switch (button.id) {
            case 0:
                this.xAxis += 10;
                break;
            case 1:
                this.xAxis -= 10;
                break;
        }
    }

    @Override
    public void drawScreen(int mx, int my, float tick) {
        this.drawDefaultBackground();
        this.drawModel();
        super.drawScreen(mx, my, tick);
    }

    public void drawModel() {
        glPushMatrix(); // Push matrix
        RenderHelper.enableStandardItemLighting(); // Enable lighting
        glTranslatef(width / 2, height / 3, 0); // Move the model to the center
        glRotatef(-25, 1, 0, 0); // Rotate the model initially to get a better view
        glRotatef(xAxis, 0, 1, 0); // Rotate the model on the x-axis with the variable xAxis
        glPushMatrix(); // Push matrix
        glEnable(GL_DEPTH_TEST); // Enable depth-testing
        glEnable(GL_BLEND); // Enable blending
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Set blend func
        glDisable(GL_CULL_FACE); // Disable depth culling
        glEnable(GL_RESCALE_NORMAL);
        glBindTexture(GL_TEXTURE_2D, mc.renderEngine.getTexture(texture)); // Bind texture
        for (int i = 0; i < this.modelbase.boxList.size(); i++) {
            ((ModelRenderer) (this.modelbase.boxList.get(i))).render(4); // Iterate over the model's parts and render them
        }
        glDisable(GL_RESCALE_NORMAL);
        glEnable(GL_CULL_FACE); // Enable depth culling
        glDisable(GL_BLEND); // Disable blending
        glDisable(GL_DEPTH_TEST); // Disable depth-testing
        glPopMatrix(); // Pop matrix
        RenderHelper.disableStandardItemLighting(); // Disable lighting
        glPopMatrix(); // Pop matrix
    }

}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment