Since this question does not specify a language (other than OpenGL), then I can provide information / advice relative to OpenGL.
Effectively, you need:
- A vertex shader: that will render screen facing quads or tiles.
- A fragment shader: that will render tile locations from a tileset texture
void main(void) {
vec2 tile = texture2D( uTilenum, vTexCoord).xy; // Choose the specific tile to use from integer texture
vec2 spriteOffset = floor( tile * 256.0 ) * uTileSize_pix; // Find the distance to the tile corner in pixels
vec2 spriteCoord = floor( mod( vPixelCoord, uTileSize_pix ) ) + vec2( 0.5, 0.5 ); // Choose the pixel within the tile
gl_FragColor = uColor * texture2D( uTileset, ( spriteOffset + spriteCoord ) * uInverseTilesetSize_pix );
}
- A menu texture: Divided into tiles as a tileset that represent your button choices. Ex: from a Simcity game I made: https://i.stack.imgur.com/mgjav.jpg
- Some form of structure that describes the menu. I personally used something similar to HTML as a format since its well known and clear. I then parse this structure with a text parser to create the menu structure. I list the source as "tile numbers" within the tileset.
<button name='button1' align='bottom left' onTouch='switchMovementMode'>
<img name='btn1img' src='74,75|90,91' width='100' height='100'></img>
</button>
<button name='button2' align='bottom left'>
<img name='btn2img' src='76,77|92,93' width='100' height='100'></img>
</button>
- A User Interface object in whatever language you are using that acts as a collection of Menu Elements and has at least a Create, CheckForInteractions, and Draw methods. The User Interface is the last Draw of each render pass, and only uses an identity matrix so there's no perspective (IE, ortho or flat to the screen). How this draws is preference, although I loop through my Menu Elements calling a sub-Draw on each. With CheckForInteractions, I personally pass the mouse / finger location to each Menu Element to check for a touch/drag/ect and have the Menu Element track its own position.
- Menu Element object that keeps track of its location, state, and responses if interacted with. May also Draw itself if the User Interface is designed that way. In my case I have each Menu Element translate and scale its ModelViewProjection matrix prior to calling Draw. Ex: interactions from a phone.
- touchAction = someTouchMethod();
- holdAction = someHoldMethod();
- dragAction = someDragMethod();
- Drawable object (can be combined with Menu Element) that keeps track of data necessary to draw a quad or tile. I personally chose glDrawElements with a drawListBuffer, which means you need:
- Four vertices put into a vertexCoordinateBuffer (Java example):
Vec3[] vC = new Vec3[]{
new Vec3( -1.0f, 1.0f, 0.0f ), // top left
new Vec3( -1.0f, -1.0f, 0.0f ), // bottom left
new Vec3( 1.0f, -1.0f, 0.0f ), // bottom right
new Vec3( 1.0f, 1.0f, 0.0f ) // top right
};
ByteBuffer vertexCoordBuffer = ByteBuffer.allocateDirect( vertexCoord.length * 3 * 4 );
vertexCoordBuffer.order( ByteOrder.nativeOrder() );
for( Vec3 v : vC ){
vertexCoordBuffer.putFloat( v.x ).putFloat( v.y ).putFloat( v.z );
}
- Four texture coordinates put into a textureCoordinateBuffer (Java example):
Vec2[] tC= new Vec2[]{
new Vec2( 0.0f, 0.0f ), // top left
new Vec2( 0.0f, 1.0f ), // bottom left
new Vec2( 1.0f, 1.0f ), // bottom right
new Vec2( 1.0f, 0.0f ) // top right
};
ByteBuffer texCoordBuffer = ByteBuffer.allocateDirect( texCoord.length * 2 * 4 );
texCoordBuffer.order( ByteOrder.nativeOrder() );
for( Vec2 c : Tc ){
texCoordBuffer.putFloat( c.x ).putFloat( c.y );
}
- A draw order list (6 items for two triangles of a quad)
int[] drawOrder = new int[]{
0, 1, 2, 0, 2, 3
};
ByteBuffer dlb = ByteBuffer.allocateDirect( drawOrder.length * 4 );
dlb.order( ByteOrder.nativeOrder() );
drawListBuffer = dlb.asIntBuffer();
drawListBuffer.put( drawOrder );
- A Draw command that chooses your compiled Vertex and Fragment shader program, performs all the pre-render assignments for Attributes and Uniforms, and then calls glDrawElements such as below:
glDrawElements( GLES30.GL_TRIANGLES, drawOrder.length, GLES30.GL_UNSIGNED_INT, drawListBuffer );