GUI in 3d

BlitzMax Modules Forums/MiniB3D/GUI in 3d

Forgive me, I am but a lowly newbie in the world of 3d programming, but I would be glad if anyone could help me a bit along here. I searched and only came up with one solution, described below, but that spawned another question.

I wish to create a FPS style game, at some point when my skills are good enough, but what is the best method of making a GUI the player sees? For instance inventory, hitpoints etc. displayed on the screen at all times.

I read somewhere it was best to use seperate poly planes and dedicated textures for it, that seems like a good idea I guess. But would these poly planes then be constricted to the camera via parenting?

Basically I'm looking for the best way to do this, I know it's a simple question but bear with me. Also if anyone can point me to some resources (preferably online, I hate books), on general 3d game design theory, such as the question posted above, I'd be even more appreciative :)

Maybe there's a unique way in bmax + minib3d to approach the gui, for instance having a regular bmax canvas in front of the minib3d graphics3d window?

All the best,
Dan

Sprites parented to the camera, and positioned just in front of it are probably your best bet.

And there's no chance that the sprite will somehow lag away from the camera during intense action and whatnot?

Just curious how it's done in all other games..

why not simply create a new camera simply for the GUI and switch between both.

why don't you knock up a simple example and try it out?
we can discuss it here...

thats a good idea bradford6 why dont you? ;p

just a thought but wouldnt it be easier to render all the 3d stuff then just use pure max2d to render your 2d content like scores etc?

I keep meaning to do a true 3d gui a bit like in sauerbraten, but somehow never get round to it

OK Chris, here you go :)

EDIT: Changed entity to a sprite and added a 'weapon' sprite to the hud.
I also added a bunch of comments that will hopefully help.

Kronhelm, play around with this.



Import sidesign.MiniB3D

Strict

Global width:Int = 1024 , height# = 768
Global midw:Int = width / 2
Global Midh:Int = height / 2

Graphics3D width,height,32,0
ClearTextureFilters

' Just create a bunch of WORLD stuff to look at
	Local box:Tentity
	For Local x:Int = 1 To 10
		For Local z:Int = 1 To 10
			box = CreateCube()
			EntityColor box , Rnd(0,200) , Rnd(0,200) , Rnd(0,200)
			PositionEntity box , x*5 , Rnd(0,20) , z*5
			EntityType box,2	
	
		Next
	Next
	box = CreateCube()
	ScaleEntity box,240,0.5,240
	EntityColor box , Rnd(0,200) , Rnd(0,200) , Rnd(0,200)
	PositionEntity box ,-120,0,-120
	EntityType box ,2


' create the player
Local player:TPlayer = Tplayer.create()

' set up collisions between the Player and the World
 Collisions(1,2,2,2) 

' Main Loopski
Repeat
			player.update()
			
			UpdateWorld
				
				'***Render To the MAP Sprite
					HideEntity player.cam
					ShowEntity player.HUDcam
										
						RenderWorld
												
						PositionEntity player.HUDcam , EntityX(player.Entity3D) , EntityY(player.Entity3D) +50, EntityZ(player.Entity3D)  
						PointEntity Player.HUDcam , player.Entity3D 
						SetColor 0,200,0
						text 0,0,"Map"
						
						BackBufferToTex(player.HUDtex)
					
					HideEntity player.HUDcam
					ShowEntity player.cam
				' *** 
			RenderWorld

			Flip 			
			
Until KeyHit(KEY_ESCAPE) 


Type Tplayer 
	
	Field Entity3d:TEntity		' the Player's pivot
	Field cam:Tentity			' the Player Camera
	Field HUDweapon:Tentity
	Field HUDweaponTex:TTexture

	
	Field HUD:Tentity		' the HUD sprite
	Field playermesh:Tentity
	
	Field HudTex:TTexture
	Field HUDcam:Tcamera
	Field xvel:Float , yvel:Float , zvel:Float
	Field xpos:Float , ypos:Float , zpos:Float
	Field xScale:Float , yScale:Float , zScale:Float
	Field Pitch:Float , yaw:Float , Roll:Float

	
	Field Camspeed:Float = 0.33
	Field CamSteps:Int = 8
	
	Field mxs:Float
	Field mys:Float
	
	Field move:Float
	Field  yspeed#
	Field  lateral#
	Field  thrust#=0.01

	Method Update()
		Get_Input()
		MovePlayer()
		
	End Method
	
	
	Method Get_Input()
			mxs# = smooth(MouseXSpeed()*Camspeed,mxs,Camsteps)
			mys# = smooth(MouseYSpeed()*Camspeed,mys,Camsteps)
			
			yaw = EntityYaw(Entity3D)-mxs
			pitch = EntityPitch(cam,1)+mys

			MoveMouse midw,midh
			MouseXSpeed() ' flush
			MouseYSpeed() ' flush
			
			If KeyDown(KEY_W) Or KeyDown(KEY_UP)=True Then move#:+thrust  ' move camera forward
			If KeyDown(KEY_S) Or KeyDown(KEY_DOWN)=True Then move#:-thrust' move camera back

			If KeyDown(KEY_A) Or KeyDown(KEY_LEFT)=True Then  lateral:-thrust' move camera left
			If KeyDown(KEY_D) Or KeyDown(KEY_RIGHT)=True Then lateral:+thrust'move camera Right
			
	End Method
		
	Method MovePlayer()
	
		RotateEntity Entity3D,0,yaw,0	' turn
		RotateEntity cam,pitch,0,0		' rotate the camera up/down
		MoveEntity Entity3D,lateral#,yspeed#,move#
		
		
				
		' convert movements into x,y,z velocities
		' this is useful for firing weapons, etc. (not used in this demo but nice to have)
		xvel = -Sin(EntityYaw(Entity3D)) * move
		zvel = Cos(EntityYaw(Entity3D)) * move
			
	
		move:*0.99		' slow down (friction)
		lateral:*0.99
		
		yspeed=-0.3	'	a constant downward pull to simulate gravity
			

	
	End Method
	
	Function Create:Tplayer()
		Local temp:Tplayer = New Tplayer
		
		temp.Entity3D = CreatePivot()	' THe Player pivot (for Y Rotation...left right)
		temp.playermesh = CreateCone(6,1,temp.Entity3D)	' the Visible Triangle on the MAP
			ScaleEntity temp.playermesh,4,4,4
			EntityColor temp.playermesh , 255,255,0
			TurnEntity temp.playermesh , 90 , 0 , 0 
		
		' This is the camera we see out of the players eyes with	
		temp.cam = CreateCamera(temp.Entity3D)
			CameraRange Tcamera(temp.cam),0.1 , 1000
		
		' Create the MAP HUD sprite and texture it.
		temp.HUD = CreateSprite(temp.cam)	' parent the MAP sprite to the Camera
			RotateEntity temp.HUD,0,0,0
			'FlipMesh tmesh(temp.HUD)
			PositionEntity temp.HUD,-1,1, 1.75
			EntityAlpha temp.HUD,0.85
			
			' I did this so the image would display oriented correctly on the screen
			' the - scale basically does a Xflip of the mesh and the flipmesh reverses the normals on the 
			' Sprite
 			ScaleSprite TSprite(temp.HUD) , 0.25, -0.25
			FlipMesh tmesh(temp.HUD)
		
		' create the texture for the MAP HUD that we will render to later
		temp.HudTex = CreateTexture(128,128)
			EntityTexture temp.hud , temp.hudtex
		
		' this is a special camera that we will use to render the HUD MAP
		temp.HUDCam:Tcamera =CreateCamera()
			RotateEntity temp.HUDCam,90,0,0
			PositionEntity temp.HUDCam,0,20,0
			CameraViewport temp.HUDCam,0,0,128,128
			CameraClsColor temp.HUDCam,0,0,0
			'CameraZoom temp.HUDcam,0.75
			CameraClsColor temp.HUDCam , 0, 0, 200
		
		
		' *** Create the HUD Weapon Sprite. you could put a picture of the current weapon here
		temp.HUDweapon = CreateSprite(temp.cam)	' parent the WEAPON sprite to the camera
			ScaleSprite TSprite(temp.HUDweapon) , 0.25, -0.25
			FlipMesh tmesh(temp.HUDweapon)
			PositionEntity temp.HUDweapon,1,1, 1.75
			EntityAlpha temp.HUDweapon,0.75
			'*** Create And render To the texture
			temp.Hudweapontex = CreateTexture(128,128)
				EntityTexture temp.HUDweapon , temp.HUDweaponTex
				HideEntity temp.cam
				ShowEntity temp.HUDcam
					Cls
					RenderWorld
					text 32,64,"WEAPON"
					BackBufferToTex(temp.HUDweapontex)
				HideEntity temp.HUDcam
				ShowEntity temp.cam
					
		' For COLLISIONS command
		EntityType temp.Entity3D , 1						' set the Palyer entity type to 1
		EntityRadius temp.Entity3D, 1.1 , 1.75
		
		' finally position the player a little 'up' so he does not fall through the world
		PositionEntity temp.entity3D , 0 , 4 , 0
		Return temp
	End Function

End Type


' this is just to reduce the jerkiness of the FPS camera.
Function Smooth#(newVal#,oldval#,steps )
		' this is used for mouselook smoothing
		If steps >1 Then oldval#=oldval#-(oldval#-newval#)/steps 
		If steps <=1 Then oldval=newval
	Return oldval#
End Function





Oh my god Bill, I think I love you. Just what I was looking for (made a quick compile). I will dissect immediately to learn from it, THANK YOU!

problem is you're clearing the whole backbuffer twice each frame, at least experiment with rendering 3d with minib3d and then using max2d for scores etc, it gonna be a lot faster

That was also my idea but I have no idea how to implement that? I mean drawing 2d max stuff on the graphics3d canvas

was under the impression that max 2d worked off the bat
but its easy enough to use gl stuff

in this example I stole some code from brls graphics to enable orthographic "mode" (ie non perspective)

superstrict

import sidesign.minib3d

graphics3d 800,600
const sx:int=400,sy:int=300

for local n:int=0 until 100
	local c:Tmesh=createcube()
	positionentity c,rand(-100,100),rand(0,100),rand(-100,100)
	rotateentity c,rand(-180,180),rand(-180,180),rand(-180,180)
	entitycolor c,rand(128,255),rand(128,255),rand(128,255)
next

local cam:Tcamera=createcamera()
positionentity cam,0,10,0
local mx:int=sx
local my:int=sy
local wx:int
local wm:int=4
local wy:int
local wym:int=1

while not keydown(key_escape)
	cls
	mx:-mousex()-sx
	my:-mousey()-sy
	movemouse(sx,sy)
	if my<-40 then my=-40
	if my>20 then my=20
	rotateentity cam,my,mx,0
	renderworld()

	BeginOrtho()
	glDisable(GL_TEXTURE_2d)
	glEnable(GL_BLEND)
	gldisable(gl_lighting)
	
	glcolor4f 1,0,1,0.5
	wx:+wm
	wy:+wym
	if wx>sx*2 or wx<0 then wm=-wm
	if wy>sy*2 or wy<0 then wym=-wym
	drawrectgl wx-20,0,40,sy*2
	glcolor4f 0,1,0,0.5
	drawrectgl 0,wy-20,sx*2,40

	EndOrtho()

	flip
wend
	
Global ortho_mv![16],ortho_pj![16]

Function BeginOrtho()
	Local vp:int[4]
	
	glPushAttrib GL_ENABLE_BIT|GL_TEXTURE_BIT|GL_TRANSFORM_BIT
	
	glGetIntegerv GL_VIEWPORT,vp
	glGetDoublev GL_MODELVIEW_MATRIX,ortho_mv
	glGetDoublev GL_PROJECTION_MATRIX,ortho_pj
	
	glMatrixMode GL_MODELVIEW
	glLoadIdentity
	glMatrixMode GL_PROJECTION
	glLoadIdentity
	glOrtho 0,vp[2],vp[3],0,-1,1

	glDisable GL_CULL_FACE
'	glDisable GL_ALPHA_TEST	
	glDisable GL_DEPTH_TEST
End Function

Function EndOrtho()
	glMatrixMode GL_PROJECTION
	glLoadMatrixd ortho_pj
	glMatrixMode GL_MODELVIEW
	glLoadMatrixd ortho_mv
	
	glPopAttrib
End Function

Function DrawRectGL( x:int,y:int,width:int,height:int )
'	BeginOrtho
	glBegin GL_QUADS
	glVertex2i x,y
	glVertex2i x+width,y
	glVertex2i x+width,y+height
	glVertex2i x,y+height
	glEnd
'	EndOrtho
End Function


You should only use textures drop backbuffer renders as a very last resort.

its easy enough to use raw opengl or nick bits of code to do what you want and FAR faster

That is a nice example Chris.


regarding my example:
You do not have to clear the backbuffer. That was just to show how to render to texture (to render the topdown map) there are 100s of ways to do this.

you can affix multiple sprites to the camera, or even affix a 3D HUD model (like a cockpit) and it will rotate with the camera movements.

One thing to try is to put actual 3D models in there as HUD elements (not sprites). I'll knock up a demo of what I mean later. I am currently making Homemade pizza with my kids. gotta get the water temp just right of the yeast won't activate...

For those, where the 'max2d in minib3d' isn't currently working. It seems that I have forgotten 1 simple command which has to be included at the top of the RenderWorld() Function.

At least there is a simple workaround without rebuilding the whole module. Add the following right before the RenderWorld()command in your main loop:

glDisable(GL_TEXTURE_2D)


Or add this at the top of the RenderWorld() function in TGlobal.bmx and rebuild the module.
That seems to bugfix a lot of the 2d bugs.

Thanks Klepto.

OK , here you go. The water volume in the cylinder will continuously decrease, when it gets toa certain point it will turn red. The idea is a visual representation of something (health, water, mana , sexual potency , whatever. depends on the game (although I am not sure I want to see a sexual potency icon !) )

The idea is to be creative and make a symbol that is consistent with the them of your game and conveys alot of meaning ( a picture *can be* worth a thousand words)




Import sidesign.MiniB3D

Strict

Global width:Int = 1024 , height# = 768
Global midw:Int = width / 2
Global Midh:Int = height / 2

Graphics3D width,height,32,0
ClearTextureFilters

' Just create a bunch of WORLD stuff to look at
	Local box:Tentity
	For Local x:Int = 1 To 10
		For Local z:Int = 1 To 10
			box = CreateCube()
			EntityColor box , Rnd(0,200) , Rnd(0,200) , Rnd(0,200)
			PositionEntity box , x*5 , Rnd(0,20) , z*5
			EntityType box,2	
	
		Next
	Next
	box = CreateCube()
	ScaleEntity box,240,0.5,240
	EntityColor box , Rnd(0,200) , Rnd(0,200) , Rnd(0,200)
	PositionEntity box ,-120,0,-120
	EntityType box ,2


' create the player
Local player:TPlayer = Tplayer.create()

' set up collisions between the Player and the World
 Collisions(1,2,2,2) 

' Main Loopski
Repeat
			player.update()
			
			UpdateWorld
				
				
			RenderWorld

			Flip 			
			
Until KeyHit(KEY_ESCAPE) 


Type Tplayer 
	
	Field Entity3d:TEntity		' the Player's pivot
	Field cam:Tentity			' the Player Camera
	Field HUDweapon:Tentity
	Field HUDweaponTex:TTexture

	
	Field HUD3D:Tentity		' the HUD 3D Model
	Field HUD3DVolume:Tentity	' this is the Water Volume insdie the cylinder
	Field Volume:Float			' the amount of liquid in the Cylinder
	Field playermesh:Tentity
	
	Field HudTex:TTexture
	Field HUDcam:Tcamera
	Field xvel:Float , yvel:Float , zvel:Float
	Field xpos:Float , ypos:Float , zpos:Float
	Field xScale:Float , yScale:Float , zScale:Float
	Field Pitch:Float , yaw:Float , Roll:Float

	
	Field Camspeed:Float = 0.33
	Field CamSteps:Int = 8
	
	Field mxs:Float
	Field mys:Float
	
	Field move:Float
	Field  yspeed#
	Field  lateral#
	Field  thrust#=0.01

	Method Update()
		Get_Input()
		MovePlayer()
		Water_Volume()
	End Method
	
	Method Water_Volume()
	
		TurnEntity HUD3D , 0 , 0.4 , 0
		
		Local change:Float = 0.0001
		volume:-change
		
		If volume<0.1 Then EntityColor HUD3DVolume,255,0,0
		
		ScaleEntity HUD3DVolume , HUD3DVolume.entityscalex(True) , Volume ,  HUD3DVolume.entityscalez(True) , True
		MoveEntity Hud3DVolume , 0 , -change*2 , 0
	End Method
	
	Method Get_Input()
			mxs# = smooth(MouseXSpeed()*Camspeed,mxs,Camsteps)
			mys# = smooth(MouseYSpeed()*Camspeed,mys,Camsteps)
			
			yaw = EntityYaw(Entity3D)-mxs
			pitch = EntityPitch(cam,1)+mys

			MoveMouse midw,midh
			MouseXSpeed() ' flush
			MouseYSpeed() ' flush
			
			If KeyDown(KEY_W) Or KeyDown(KEY_UP)=True Then move#:+thrust  ' move camera forward
			If KeyDown(KEY_S) Or KeyDown(KEY_DOWN)=True Then move#:-thrust' move camera back

			If KeyDown(KEY_A) Or KeyDown(KEY_LEFT)=True Then  lateral:-thrust' move camera left
			If KeyDown(KEY_D) Or KeyDown(KEY_RIGHT)=True Then lateral:+thrust'move camera Right
			
	End Method
		
	Method MovePlayer()
	
		RotateEntity Entity3D,0,yaw,0	' turn
		RotateEntity cam,pitch,0,0		' rotate the camera up/down
		MoveEntity Entity3D,lateral#,yspeed#,move#
		
		
				
		' convert movements into x,y,z velocities
		' this is useful for firing weapons, etc. (not used in this demo but nice to have)
		xvel = -Sin(EntityYaw(Entity3D)) * move
		zvel = Cos(EntityYaw(Entity3D)) * move
			
	
		move:*0.99		' slow down (friction)
		lateral:*0.99
		
		yspeed=-0.3	'	a constant downward pull to simulate gravity
			

	
	End Method
	
	Function Create:Tplayer()
		Local temp:Tplayer = New Tplayer
		
		temp.Entity3D = CreatePivot()	' THe Player pivot (for Y Rotation...left right)
		temp.playermesh = CreateCone(6,1,temp.Entity3D)	' the Visible Triangle on the MAP
			ScaleEntity temp.playermesh,4,4,4
			EntityColor temp.playermesh , 255,255,0
			TurnEntity temp.playermesh , 90 , 0 , 0 
		
		' This is the camera we see out of the players eyes with	
		temp.cam = CreateCamera(temp.Entity3D)
			CameraRange Tcamera(temp.cam),0.1 , 1000
		
		' Create the 3D Cylinder HUD object.
		temp.HUD3D = CreateCylinder(8,1,temp.cam)	' parent the MAP sprite to the Camera
			'RotateEntity temp.HUD,0,0,0
			'FlipMesh tmesh(temp.HUD)
			EntityColor temp.HUD3D , 0,200,0 ' Green
			
			PositionEntity temp.HUD3D,-0.95,0.65, 1.25
			EntityAlpha temp.HUD3D,0.65
			ScaleEntity temp.HUD3D , 0.2,0.2,0.2
		
		' Create the 3D Cylinder Volume Object that will be our visual referenc for Health
		temp.HUD3DVolume = CreateCylinder(8,1,temp.HUD3D)
			EntityColor temp.HUD3DVolume , 0,00,100 ' Blue
			EntityAlpha temp.HUD3DVolume,0.75
			ScaleEntity temp.HUD3DVolume , 0.175,0.175,0.175,1
			temp.Volume = 0.175

			
		
		' this is a special camera that we will use to render the HUD MAP
		temp.HUDCam:Tcamera =CreateCamera()
			RotateEntity temp.HUDCam,90,0,0
			PositionEntity temp.HUDCam,0,20,0
			CameraViewport temp.HUDCam,0,0,128,128
			CameraClsColor temp.HUDCam,0,0,0
			'CameraZoom temp.HUDcam,0.75
			CameraClsColor temp.HUDCam , 0, 0, 200
			
			CameraClsMode temp.HUDCam,False,True
		
		
		' *** Create the HUD Weapon Sprite. you could put a picture of the current weapon here
		temp.HUDweapon = CreateSprite(temp.cam)	' parent the WEAPON sprite to the camera
			ScaleSprite TSprite(temp.HUDweapon) , 0.25, -0.25
			FlipMesh tmesh(temp.HUDweapon)
			PositionEntity temp.HUDweapon,1,1, 1.75
			EntityAlpha temp.HUDweapon,0.75
			'*** Create And render To the texture
			temp.Hudweapontex = CreateTexture(128,128)
				EntityTexture temp.HUDweapon , temp.HUDweaponTex
				HideEntity temp.cam
				ShowEntity temp.HUDcam
					Cls
					RenderWorld
					text 32,64,"WEAPON"
					BackBufferToTex(temp.HUDweapontex)
				HideEntity temp.HUDcam
				ShowEntity temp.cam
					
		' For COLLISIONS command
		EntityType temp.Entity3D , 1						' set the Palyer entity type to 1
		EntityRadius temp.Entity3D, 1.1 , 1.75
		
		' finally position the player a little 'up' so he does not fall through the world
		PositionEntity temp.entity3D , 0 , 4 , 0
		Return temp
	End Function

End Type


' this is just to reduce the jerkiness of the FPS camera.
Function Smooth#(newVal#,oldval#,steps )
		' this is used for mouselook smoothing
		If steps >1 Then oldval#=oldval#-(oldval#-newval#)/steps 
		If steps <=1 Then oldval=newval
	Return oldval#
End Function









Replace the Hud3D part and Hud3DVolume part with the following:
' Create the 3D Cylinder Volume Object that will be our visual referenc for Health
		temp.HUD3DVolume = CreateCylinder(8,1,temp.HUD3D)
			EntityColor temp.HUD3DVolume , 0,00,100 ' Blue
			EntityAlpha temp.HUD3DVolume,0.75
			ScaleEntity temp.HUD3DVolume , 0.175,0.175,0.175,1
			temp.Volume = 0.175
			EntityOrder temp.Hud3DVolume,0
			
		
		' this is a special camera that we will use to render the HUD MAP
		temp.HUDCam:Tcamera =CreateCamera()
			RotateEntity temp.HUDCam,90,0,0
			PositionEntity temp.HUDCam,0,20,0
			CameraViewport temp.HUDCam,0,0,128,128
			CameraClsColor temp.HUDCam,0,0,0
			'CameraZoom temp.HUDcam,0.75
			CameraClsColor temp.HUDCam , 0, 0, 200
			
			CameraClsMode temp.HUDCam,False,True


this keeps the Hud always in front of every other object.

thanks Klepto.
try
EntityOrder temp.Hud3D, -1
EntityOrder temp.HUD3DVolume , -1


as well. this turns off Z sorting and in this case always draws those things on top.

Forgive me - I'm new to 3d.

But, I poured over your code and I can't see where you actually "drew" the "ground"...

If someone could point me there, I'd very much appreciate it!

Oldbone,

The ground in the example is simply a cube stretched significantly in the x and z directions and positioned appropriately:

box = CreateCube()
ScaleEntity box,240,0.5,240
EntityColor box , Rnd(0,200) , Rnd(0,200) , Rnd(0,200)
PositionEntity box ,-120,0,-120
EntityType box ,2

It is then drawn along with all the other cubes whenever the Renderworld command is issued. (Every object when created or loaded is added automatically to the rendering list.)