3D games with Monkey + MiniB3D

Monkey Archive Forums/Monkey Tutorials/3D games with Monkey + MiniB3D

Midimaster(Posted 2013) [#1]
This thread will become a continuing tutorial about the Monkey Module MiniB3D. Every lesson will be become one post, more chapters will follow,

The tutorial is for beginners and for users, who are already used to work with the old Blitz3D. It will show the differences in coding with Monkey. All samples will run on HTML5 with the free version of Monkey.

This chapters are ready:
- Chapter 1: Download the Module


- Chapter 2: Test a first sample code



- Chapter 3: More Entities


- Chapter 4: Parents and Children



- Chapter 5: Easy Collisions


- Chapter 6: Textures



- Chapter 7: Sprites




and here is ....


Chapter 1: Download the Module

For the current version of MiniB3D you also need the current version of Monkey. So at first update your Monkey.

The MiniB3D module is now avaiable as a ZIP file. Download it from here...

https://github.com/adamredwoods/minib3d-monkey/archive/master.zip

...and extract it to your Monkey Module folder...

C:\MonkeyPro69\modules\


After this the minib3d folder will have the wrong name:

C:\MonkeyPro69\modules\minib3d-monkey-master\

Rename it to "minib3d"...

C:\MonkeyPro69\modules\minib3d\

That's all!


Midimaster(Posted 2013) [#2]
Chapter 2: Test a first sample code:

As a first test, the following sample code should run without problems:


(Screenshot)


You will see a turning cube in a blue empty world. This sample code can be a template for your future minib3d projects. It does not contains the minimal possible code template for midib3d, but it contains all elements you need for watching something on the screen: A world, a camera, a light and a object.

On the top it starts with the directive Strict which is alway recommended for preventing typos in your code. Then it imports two essential modules "mojo" and "minib3d".

As all monkey projects you need to define a Game() class and a Main() function, which calls your Game() class.

The three essential 3D elements TCamera, TLight and TMesh are defined as Fields in the Game Class. They will be invoked in a Init() Method later. Normally all game elements are invoked in the OnCreate() method, but with Minib3D you can do this only when the app call for the first time the OnRender() method.

So, you see in OnCreate() nearly nothing happens. The command SetRender() creates the 3D world. Also OnUpdate() is returned immediately until the Init() is not yet started.

Now, the first OnRender() call is important for MiniB3D. In this moment you call your method Init() to set and invoke your 3D elements. After this you can set Camera, Light and a first Object.

A variable Started% prevents not to call this Init() again.

After this first call of Init() in OnRender() you now can work with your 3D world.

The command RenderWorld() always renders the current state of all 3D elements to the screen and is indispensable. Also the call of UpdateWorld() in OnUpdate() is essential.


...to be continued....


Midimaster(Posted 2013) [#3]
Chapter 3: More Entities

A 3D object in MiniB3D is called "Entity". You can create them new by using primitives or by copying existing entities.

This primitives are avaiable:
CreateGrid(xSsegments, ySegments, Repeat, Parent) ' a plane
CreateCube(Parent) ' a cube or a block
CreateSphere(Segments,Parent) ' a bowl or ellipsoid
CreateCylinder(Segments, Solid, Parent) ' a cylinder or tube
CreateCone(Segments, Solid, Parent) ' a cone
CreateLine(Vector1, Vector2, Thickness, Parent) ' a line


As there is no documentation of MiniB3D at the moment you can have a look on the sourcecode files to inform you about all features and functions. Click on the tab "Projects" in monkey's browser on the right side: There open "modules - minib3d -tmesh.monkey". Now change to the browser tab "Code" and open the tree "TMesh". With a click on a command you will see the corresponding source code on the left and in the first code line with the function call you can inform about parameters:

for example: "modules - minib3d - tmesh.monkey - CreateSphere"
	' Function by Coyote
	Function CreateSphere:TMesh(segments:Int=8,parent_ent:TEntity=Null)

		If segments<2 Or segments>100 Then Return Null
		....

This means, that the CreateSphere() command has two parameters. The first one is the resolution quality and a second gives you the possibility to add this Entity to a parent entity.



The "Entities" can be manipulated in many ways. A look into the source code of "modules - minib3d - tentity.monkey" shows us all methods. Here are the most important:

.CopyEntity() ' creates a new entity from a existing one
.X() ' return the coordinates of the entity
.Y() "
.Z() "
.PositionEntity(X,Y,Z) ' puts the entity on a new coordinate
.MoveEntity(addX,addY,addZ) ' moves the entity 
.ScaleEntity(X,Y,Z) 'sizes the entity
.RotateEntity(X,Y,Z) ' puts the entit i a new orientation
.TurnEntity(addX,addY,addZ) ' turns the entity
.EntityColor(R,G,B) 
.ShowEntity() '
.HideEntity()
....



In the following sample code you can see some of them in action. It's a world of buildings made by CopyEntity(). The TurnEntity() and MoveEntity() will drive the camera like a car . Use the MouseX() to control direction.




Sammy(Posted 2013) [#4]
Nice! :)


Midimaster(Posted 2013) [#5]
Chapter 4: Parents and Children

If you already worked with the old Blitz3D or MiniB3D on BlitzMax you will recognize a lot of the commands and syntaxes in the new Monkeys MiniB3D.

But if you are completely new to 3D programming you will miss the documentation. As a makeshift you may find helpful the vintage manual of Blitz3D at...

sorted by category:
http://www.blitzmax.com/b3ddocs/command_list_3d_cat.php

sorted by A-Z:
http://www.blitzmax.com/b3ddocs/command_list_3d_a-z.php


You can create more complex 3D-objects by combining the basic primitives. Therefore you need to organize them in dependecies. The biggest object is a parent and smaller parts of the object are added as childrens. If you now move the parents they childrends will stay connected with the parent and all move together like one 3D object:


(Screenshot)

a tank made of a Cube() as parent and two Spheres()as children:
		Tank= CreateCube()
		Tank.ScaleMesh 1.5,0.4,2
		
		Dome=CreateCylinder(8,True,Tank)
		Dome.MoveEntity 0,.6,0
		Dome.ScaleMesh 1.3,0.4,1.5

		Gun=CreateCylinder(8,TRUE,Dome)
		Gun.ScaleMesh 0.2,2,0.2
		Gun.TurnEntity 90,0,0
		Gun.MoveEntity 0,2,0


A copy of the Gun is added to the camera:
		Camera = CreateCamera()
		...
		Local Gun2:TEntity=Gun.CopyEntity(Camera)
		Gun2.MoveEntity 0,-1,1



If you now move the tank, the dome will follow. But it is also possible to turn the dome within the tank:

		Tank.TurnEntity 0,.5,0
		Tank.MoveEntity 0,0,.1
		...
		Dome.TurnEntity 0,Dir,0



A tank game:



to be continued....


Gerry Quinn(Posted 2013) [#6]
Finally a tutorial! Thanks!!!

I did try MiniB3D and get an example working, but even though I have used Blitz3D a little I found MiniB3D a bit intimidating.


donicamm(Posted 2013) [#7]
Thank you for creating this. I got it running and am quite happy with it, since I've got some experience with Blitz3D.

One question though, does anyone get slight warping or distortion of the shaped in the building flying example? It seems like as I get closer to the objects they kind of stretch to reach out at me.


AdamRedwoods(Posted 2013) [#8]
It seems like as I get closer to the objects they kind of stretch to reach out at me.

perspective cameras will do that, as it mimics real life lenses. one way to change that is to set the Camera.CameraZoom(2.0).


Midimaster(Posted 2013) [#9]
Chapter 5: Easy Collisions

Objects in 3D games are not massive by default. Every object can go through the other objects:



To prevent from doing that you have to define a invisible "room" around the center of each object. Its dimensions define the border to other objects.

As a second step you have to add each object to a "group of same objects". These object later will have the same collision behavior.

As a third step to have to define which collisions between which groups should be controlled by the 3D-engine.



Step 1: The room
The collision room should correlate with the visible shape of the object.
The Bowl gets a room like a sphere:
MovingObjects%=1
Bowl.CollisionSetup MovingObjects, COLLISION_METHOD_SPHERE


Also these types are avaiable:
COLLISION_METHOD_SPHERE
COLLISION_METHOD_POLYGON
COLLISION_METHOD_BOX


So our cube gets:
Bowl.CollisionSetup MovingObjects, COLLISION_METHOD_BOX




Step 2: The Group
You can join all object to only one group. Then the collision check has to do a lot, because it has to check every object with all the others. But it would work...

It is better to differ the object into groups, because some can never collide. fe.e trees with trees. So it would be useless always to check collisions between them. And some always collide, but it does not matter. f.e. trees and the ground. So think about useful groups to minimize the work the engine will have with collisions.

In our sample we only have one group and we call it MovingObjects%=1
local MovingObjects%=1
Bowl.CollisionSetup MovingObjects, COLLISION_METHOD_SPHERE
Cube.CollisionSetup MovingObjects, COLLISION_METHOD_BOX




Step 3: Collision Pair
At last we define a collision check between all moving objects:

Collisions MovingObjects,MovingObjects, COLLISION_METHOD_SPHERE, COLLISION_RESPONSE_STOP


If a collision happens, there can be various reactions. The object can stop immediately. Or they could change their course and slide along until they do not collide anymore.

This reactions are possible:
COLLISION_RESPONSE_NONE
COLLISION_RESPONSE_STOP
COLLISION_RESPONSE_SLIDE
COLLISION_RESPONSE_SLIDEXZ




All these steps have to be prepared in the Init-Method.
	Method Init:Void()		
		....

		Local MovingObjects%=1

		Cube= CreateCube()
		Cube.PositionEntity -0.5,-2.5,8
		Cube.CollisionSetup MovingObjects,COLLISION_METHOD_SPHERE

		Bowl= CreateSphere()
		Bowl.PositionEntity 0,5,8
		Bowl.EntityColor 255,0,0
		Bowl.CollisionSetup MovingObjects,COLLISION_METHOD_SPHERE

		Collisions MovingObjects,MovingObjects, COLLISION_METHOD_SPHERE, COLLISION_RESPONSE_STOP



At the end the engine will automatically check and handle the collision at the command UpdateWorld() in our OnUpdate():
	Method OnUpdate%()
		If KeyHit(KEY_CLOSE) Or KeyHit(KEY_ESCAPE) Then Error ""
		If Not Started Then Return 0
		Cube.TurnEntity 0,1,0
		Bowl.MoveEntity 0,-0.1,0
		Print Bowl.CountCollisions()
		UpdateWorld()
		Return 0
	End

If you want to be informed about any collision, you could check the CountCollisions() of your object.


Box stopps bowl:


to be continued.....


Midimaster(Posted 2013) [#10]
Chapter 6: Textures

If we want to add textures to the entities, we have to pre-load all images first, then load each image into a texture. Then add the texture to a entity and scale the texture on the surface.


(Screenshot)

For that we need some changes in our basic game code:

The Init() needs to ensure, that the loading of the images is already finished, before we try to create textures. Therefore MiniB3D has a function TPixmap.PreLoadPixmap():
	Method Init:Void()		
		If Started Then Return 1

		If Not TPixmap.PreLoadPixmap(["Ground.png","Sky.png"])	
			Return  
		Endif
Print "Preload finished"
....		

You call this function with an string array of all needed images. The function will pre-load the images and exit the Init() as long as one image is still missing.

For this chapter you need two texture-images. You will find them here:

Ground.png:


Sky.png:


Copy both into your MyGame.data folder!


Now we are nearly ready. The rest is easy... Load the textures, scale them and add them to an entity:
		Floor=CreateGrid(15,15)
		Floor.ScaleEntity 10,1,10 
		Floor.PositionEntity -2,-2.5,8

		Local Ground:TTexture=LoadTexture( "Ground.png" )
		Ground.ScaleTexture .3,.3
		Floor.EntityTexture Ground


When you create a sky sphere you additional have to flip the mesh to see the texture from "inside" the bowl:
		Sky=CreateSphere(8)
		Sky.ScaleEntity 150,50,150

		Local Heaven:TTexture=LoadTexture( "Sky.png" )
		Sky.EntityTexture Heaven
		Sky.FlipMesh
		Sky.EntityFX FXFLAG_FULLBRIGHT


Also the sky should not be affected by the light direction, we have to set it's FX to FULLBRIGHT

Here is the complete code: landscape with sky:



Midimaster(Posted 2013) [#11]
Chapter 7: Sprites

Sprites are flat 2D objects in a 3D space. Often they are permanent faced toward the camera. Sprites are used for object, that have the same look from all sides like trees. Or for objects that are connected to the camera.

In our sample we will see a forest of flat trees, which will look like 3D trees. And we will see the optic effect of sun reflections in our camera lens. The third sprites shows us, how to bind "buttons" to the camera.


(Screenshot)


A Sprite has to be preloaded the same way like textures....
	Method Init:Void()		
		If Started Then Return
		If Not TPixmap.PreLoadPixmap(["tree.png","sun1.png","sun2.png","sun3.png","sun4.png","text.png"])	
			Return
		Endif
....


Here you can download the 6 images:

tree.png:




sun1.png:

(it looks like empty, but isn't!)


sun2.png:

(it looks like empty, but isn't!)


sun3.png:

(it looks like empty, but isn't!)


sun4.png:

(it looks like empty, but isn't!)


text.png:



After pre-loading the images we can load it into a TSprite Type.
Class Game Extends App
	
	Field Camera:TCamera
	Field Light:TLight
	Field Floor:TMesh
	Field Sun1:TSprite, Sun2:TSprite, Sun3:TSprite, Sun4:TSprite
	Field Tree:TSprite, Button:TSprite
	Field Started:Bool
....

	Method Init:Void()	
		.....
		Tree = LoadSprite("tree.png",2)
		.....
		Button=LoadSprite("text.png",2,Camera)
		.....
		Sun1 = LoadSprite("sun1.png",2,Camera)
		.....
....




A forest:

To produce a lot of objects of the same image we can copy the sprite entity:

	Method Init:Void()	
		.....

		For Local i%=0 To 200
			Local locTree:TEntity = CopyEntity(Tree)
			Local YScale#=Rnd(.3,1.5)
			Local x#,y#
			x=Rnd(30)
			y=Rnd(30)

			locTree.PositionEntity x,YScale,y
			locTree.ScaleEntity Rnd(0.3,1),YScale,1
			locTree.EntityColor Rnd(0,255),Rnd(230,255),Rnd(150,205)
		Next

You handle those copies like independent entities and define a different size, color or position for each copy of the original sprite.



Lens reflections

We add four sprites in front of our camera and will move them left or right depending of the camera's direction. This causes a lens reflection effect.
	Method Init:Void()	
		.....

		Sun1 = LoadSprite("sun1.png",2,Camera)
		Sun1.EntityFX 9 
		Sun1.ScaleSprite 2,2
		Sun1.EntityColor 255,255,242

		Sun2 = LoadSprite("sun2.png",2,Camera)
		Sun2.EntityFX 9 
		Sun2.ScaleSprite .4,.4
		Sun2.EntityColor 255,255,220

		Sun3 = LoadSprite("sun3.png",2,Camera)
		Sun3.EntityFX 9 
		Sun3.ScaleSprite .7,.7
		Sun3.EntityColor 255,255,200

		Sun4 = LoadSprite("sun4.png",2,Camera)
		Sun4.EntityFX 9 
		Sun4.ScaleSprite .7,.7
		Sun4.EntityColor 255,255,180


They are moved in our MovingCamera() method:
	Method MovingCamera:Void()
		.....

		Local Angle#=((EntityYaw(Camera)+36000) Mod 360)-180
		Local alpha#=1.0-Abs(Angle)/60.0
	
		Sun1.EntityAlpha alpha
		Sun2.EntityAlpha alpha
		Sun3.EntityAlpha alpha
		Sun4.EntityAlpha alpha
	
		Sun1.PositionEntity  0.05*Angle,1.5,2
		Sun2.PositionEntity -0.017*Angle,-0.8,2
		Sun3.PositionEntity 0,-0.8,2
		Sun4.PositionEntity -.034*Angle,-1,2



A control "button"

Closed to our camera there shall be a text. The nearest possible distance between objects and camera is 1. So we put this texture on Z=1.01:
	Method Init:Void()		
.....
		Button=LoadSprite("text.png",2,Camera)
		Button.ScaleEntity 1.01,.1,0.01
		Button.MoveEntity 0,-0.7, 1.01




the complete code Sprite.monkey:




Sammy(Posted 2013) [#12]
Another excellent tutorial tutorial, thank you! :)


Raul(Posted 2013) [#13]
Just tried the Sprite example and is not working properly. The GLFW target crashed my nvidia drivers and html5 returned:

WebGL 1.0 (OpenGL ES 2.0 Chromium)
**WEBGL 1

..FullShader success
Monkey Runtime Error : Rendering operations can only be performed inside OnRender
C:/MonkeyPro69/modules/mojo/graphics.monkey<48>
C:/MonkeyPro69/modules/mojo/graphics.monkey<387>
D:/projects/testandother/Project3D/main.monkey<123>
C:/MonkeyPro69/modules/mojo/app.monkey<67>


Midimaster(Posted 2013) [#14]
Line 123 is inside the OnRender(). It was a relict from some bug tests. At me it worked perfect also with this line.

To be on the save side, I now removed the line. Please copy and paste the new code and test it again.

I tested the new sample code with my Monkey V69 and MiniB3D V38 (new version from yesterday) on target HTML5... And it works.

Did you perhaps add anything? The original code renders 100% nothing outside the OnRender().

What about your updates? Do you work with the latest version?

What about the other chapter's codes? Are they working?

Only for a test I would suggest to return OnUpdate() and see what happens:
Method OnUpdate%()
	If KeyHit(KEY_CLOSE) Or KeyHit(KEY_ESCAPE) Then Error ""
	If Not Started Then Return 0
	Return 0
End



Raul(Posted 2013) [#15]
Yeah, it seems this was the issue:
DrawRect 100,100,200,200


But I found something new. Testing on html5 it is working great.

I compiled and tried on Android. My floor mesh is white and I do not see the button.

Any idea why?


Midimaster(Posted 2013) [#16]
Thank you for your report...

I think I need a little bit more information. Which version of Monkey? ...Android? ...MiniB3D?

A white floor sounds like a module bug. I think you should send a copy of your bug report to the minib3d thread too:

http://www.monkeycoder.co.nz/Community/posts.php?topic=5016


The button problem can be caused, because it has a very closed position to the camera. Can you please test this:

		Button=LoadSprite("text.png",2,Camera)
		Button.ScaleEntity 1.01,.1,0.01
		Button.MoveEntity 0,-0.7, 2 '<-new value=2


if this works, try also this values: 1.50 then 1.10 then 1.05 then 1.02


Raul(Posted 2013) [#17]
I have Monkey 69 version with the last MiniB3D. I ran the software on a Samsung tablet (GT-N8013) with Android 4.1.1

Regarding the buttons:
2 and 1.5 are working ok. Although some of the trees will come between text and camera :)


Sammy(Posted 2013) [#18]
Midimaster, any chance of some more tutorials please? Animation for example?


Sammy(Posted 2013) [#19]
I'm having problems with collisions, a sphere dips into the corners of a cube. Does this in the collision sample too?


AdamRedwoods(Posted 2013) [#20]
I'm having problems with collisions, a sphere dips into the corners of a cube.

working on that now, but also make sure your collisions are using "box" or "polygon" and not "sphere"
Collisions( 1, 2, COLLISION_METHOD_BOX, COLLISION_RESPONSE_SLIDEXZ )



Midimaster(Posted 2013) [#21]
More tutorials are coming soon. I have a tutorial about loading a 3D multitexture landscape from a *.B3D file nearly ready, but at the moment Adam does some fundamental updates because of that.


Sammy(Posted 2013) [#22]
@Adam: ah, thank you Adam. :)

@Midimaster: Great stuff. I'll look forward to that too!

Again, thanks guys.


DiabloV(Posted 2013) [#23]
i have the minib3d in my folder (D:\MonkeyPro\modules\minib3d)

but a get an error on compilation the fist sample :

"D:/MonkeyPro/bin/transcc_winnt" -target=Glfw_Game -config=Release -run "D:/DEV/momo2/momo44.monkey"
TRANS monkey compiler V1.56
Parsing...
D:/DEV/momo2/momo44.monkey<3> : Error : Module 'minib3d' not found.
Done.


Midimaster(Posted 2013) [#24]
1.
Is "D:\MonkeyPro\" the folder, where your "Monkeyxx.Exe" is?

2.
Can you see "minib3d.monkey" inside the folder "D:\MonkeyPro\modules\minib3d\"? Or is there another sub-folder?

3.
Did you already try to destroy the "momo44.build" folder in D:/DEV/momo2/"?

4.
Have a look on the right side of the code editor TED: "Browser - Projects - Monkeyxx - modules -". Can you see here the minib3d folder?


5.
Did you try one of the examples of "D:\MonkeyPro\modules\minib3d\examples" or the tutorial chapter 2 example?

6.
If you tested 1. to 5., write a bug report post to this thread:

http://www.monkeycoder.co.nz/Community/posts.php?topic=5703


DiabloV(Posted 2013) [#25]
1 : yes
2 : no, minib3d.monkey don't exsit in any folder
3 : yes
4 : yes
5 : no, its only *.bmx


DiabloV(Posted 2013) [#26]
hey, it'es ok, the *.zip was not the good. ?

Thanks, it's closed.


En929(Posted 2016) [#27]
WOW, this is an awesome tutorial. I'm glad I found this. I got things going in minutes. I new to 3D game development!


ratking(Posted 2016) [#28]
You got this to run? I constantly get errors like "Native OS Module not implemented", or "Method TColTree.new() is private." and can't even get the second tutorial to run.


AdamRedwoods(Posted 2016) [#29]
That's an old error, that my lazy butt needs to fix.
For now, you can comment out the "Import OS" line, and in file "tcoltree.monkey" the New Method can be moved above into the public area.


ratking(Posted 2016) [#30]
Thanks! My own lazy ass actually solved these issues already, but the HTML5 target still has an error: "Unable to find overload for glDrawElements(Int,Int,Int,DataBuffer)."

Edit: Okay, seems like enclosing the offensive lines with #if TARGET<>"html5" ... #end helps!


Leo Santos(Posted 2016) [#31]
Thanks for the tutorials!
I managed to get the errors to go away in HTML5, but nothing renders. :-(

I had never checked MiniB3D, and I have to say it looks pretty awesome, but developing for GLFW only is pretty slow (takes a few seconds to compile even a simple example).
Any chance this will ever be updated/maintained? Maybe updated to Mojo2?

Seriously, I'd pay money for this!
Leo.


AdamRedwoods(Posted 2016) [#32]
I'm thinking about it.