|
|
|
|
Reply From: |
wombatstampede |
I use better collada exporter (I still use Blender 2.79).
First: My game runs in GLES2 and is also targeted on mobile devices. So maybe my requirements are very different from yours.
When I create models in Blender, I try to use material names which I also want to keep in Godot that way. So I also reuse materials from other models. And try to avoid using the same names for different materials accross different models. (Avoid “Material.001”)
I also normally directly use the textures from the godot project directory. (At least same name / path structure may be advisable.)
I export the models with “Triangulate”, “apply modifiers”, uncheck “copy images” (they’re already there).
Then I created an import script for import on godot. (You can put that in the import settings)
On Materials I usually select storage “Mesh” and “Built in”. Storage also to “Single scene”.
Compression is usually enabled. But for bigger meshes (roughly >50m) I need to disable it when accuracy is needed. (I.e. when terrain tiles need to match.)
The import script has some tool functions which I apply to imported models. This has meanwhile grown to quite a big number. Such a script gets passed the imported scene and you can use gdscript to do many things each time an import takes place.
One of these functions scans the materials.
- if they contain textures then I scan the specific res:// path where I put such materials (recursively) for fitting a material name. If that doesn’t succeed then I check if I find the texture file (from the resource path in the material or the surface name) and assign the correct texture path to the material.
- Certain material names are matched against known shader names (terrain, water …) and replaced.
- If the materials are albedo only, I search for albedo materials with that surface name in my res:// path and assign them. If nothing is found, I print out warning and keep the materials (I didn’t manage to save new materials from such an import script so I manually save new materials after import once when required.)
I also do a number of other processing tasks in the script. Naturally, those depend on the type of game/app.
This includes:
- Assigning of physics attributes / joints (sometimes depending on markers in the object names)
- Auto-Creating/Assigning collision shapes for certain surface names.
- Auto recreating/adjusting UVs for certain surface names.
- Auto removing “cast shadow” for certain object/surface names.
- Auto creating “shadow only” meshes for certain surfaces.
I usually set cast shadow to off for bigger terrains. Where shadow is useful (i.e. vertical walls) but the part is not a separate mesh but a part/material of a bigger mesh I create a shadow only copy of that surface. This probably is a very specific use case but has considerably helped with performance for me (especially on mobile devices and low performing PCs).
could you show me example code of import script for e.g:
" Auto-Creating/Assigning collision shapes for certain surface names." ?
beetbeet | 2019-04-15 16:10
This script is only for a specific use case. Those collision shapes are for vertical “walls” where the lower edge is horizontals. The material has “-collide” in its name. Also there’ll have to be objects with “-preset” in their names to set values like collision mask and layers.
Example (generated collision shapes are bright blue lines in marked areas):
This is very far from general purpose but perhaps you find some inspiration in it.
I stripped down the code did not test that:
tool
extends EditorScenePostImport
var mdt
var preset={}
var presetMatch
const MATERIAL_BASE = "res://resources/materials/"
func post_import(_scene):
mdt = MeshDataTool.new()
presetMatch = RegEx.new()
presetMatch.compile("-(\\w+)=(-?[0-9.]+)")
print("** getCollisionNode")
getCollisionNode(_scene,_scene)
print ("*** done ***")
return _scene
func getCollisionNode(node,scene):
var delList = {}
for N in node.get_children():
if N.get_child_count() > 0:
getCollisionNode(N,scene)
if N is CollisionObject:
setBodyPresets(N)
# build collision shapes "box" for "walls" when material contains "-collide" flag in name
# * mesh has to consist of tris (check on blender collada export)
# * "wall" surface "quad" consists of 2 tris
# * "bottom" tri is required to have 2pts of equal height (y-value)
if (N is MeshInstance):
var has_collide = false
var m = N.get_mesh()
for s in range(0,m.get_surface_count()):
if (m.surface_get_name(s).find("-collide")>=0):
if m.surface_get_primitive_type(s) == Mesh.PRIMITIVE_TRIANGLES:
has_collide=true
else:
print("Error: wrong surface type "+String(m.surface_get_primitive_type(s))+
" for "+m.surface_get_name(s)+". Required: PRIMITIVE_TRIANGLES")
if has_collide: #collide material found
var cscount=0
var sbs={}
for s in range(0,m.get_surface_count()):
if (m.surface_get_name(s).find("-collide")>=0):
var newname=m.surface_get_name(s).replace("-collide","")
mdt.create_from_surface(m, s)
for f in range(mdt.get_face_count()):
var v=[]
var cwid = 0.5 #width of collision shape (away from normal)
for vc in range(3):
v.append(mdt.get_vertex(mdt.get_face_vertex(f,vc)))
var minY=min(v[0].y,min(v[1].y,v[2].y))
var maxY=max(v[0].y,max(v[1].y,v[2].y))
var vxz=[]
for vc in range(3): #make array of 2 verts at minY
if abs(v[vc].y-minY)<0.01:
vxz.append(v[vc])
if vxz.size()==2: #not "upper rectangle" (which has only 1 vert at minY)
var norm=mdt.get_face_normal(f)
#y-values are the same so this is length in xz plane
var xzVec=vxz[1]-vxz[0]
var lenxz=xzVec.length()
var midxzOffs=xzVec/2
var cshape = CollisionShape.new()
var bshape = BoxShape.new()
#node.add_shape(bshape,N.get_transform())
cscount=cscount+1
cshape.set_name(newname+"_"+String(cscount))
cshape.set_shape(bshape)
var sb = GetCollStaticBodyXY(scene,N,sbs,vxz[0].x+midxzOffs.x-(norm.x*(cwid/2)),\
vxz[0].z+midxzOffs.z-(norm.z*(cwid/2)))
sb.add_child(cshape)
#you need to set_owner to scene root to make a node visible in editor
cshape.set_owner(scene)
bshape.set_extents(Vector3(cwid/2,(maxY-minY)/2,lenxz/2))
#center of new shape
cshape.set_translation(Vector3(vxz[0].x+midxzOffs.x-(norm.x*(cwid/2)),
minY+((maxY-minY)/2),vxz[0].z+midxzOffs.z-(norm.z*(cwid/2))))
cshape.rotate_y(Vector2(xzVec.x,xzVec.z).angle_to(Vector2(0,-1)))
mdt.clear()
# Create collision shape for a box-object inside a -colonly (StaticBody) or -rigid (RigidBody) parent
# * object name contains "-colbox"
# * client object rotation is not transferred (if any)
# * shape has extents and position of box
# * removes automatically created shapes ("shape","SphereShape","BoxShape")
if N is MeshInstance and (N.name.find("-colbox")>=0):
print(node.name+" ->"+N.name)
var newname=N.name.replace("-colbox","")
if node is CollisionObject: #parent can deal with collision shapes
var aab=N.get_aabb()
var cshape=null
var bshape=null
#reuse/update shape if already exists, does this ever happen?
if node.has_node(newname) and (N is CollisionShape):
cshape = node.get_child(newname)
bshape = cshape.get_shape()
print(" update existing")
else:
cshape = CollisionShape.new()
bshape = BoxShape.new()
cshape.set_name(newname)
cshape.set_shape(bshape)
node.add_child(cshape)
#you need to set_owner to scene root to make a node visible in editor
node.get_node(newname).set_owner(scene)
#print(" add new shape "+node.get_parent().get_name()+'-'+node.get_name()+"-"+newname+":"+String(node.has_node(newname)))
bshape.set_extents(aab.size/2)
#no rotation transferred
cshape.set_translation(N.get_translation()+((aab.position+(aab.size/2))))
node.remove_child(N) #maybe not required when free/queue_free is used
N.queue_free()
#remove shapes which are auto-added to rigidbodies(-rigid)/staticbodies (-colonly)
#they're replaced by colbox shapes
delList["shape"]="CollisionShape"
delList["SphereShape"]="CollisionShape"
delList["BoxShape"]="CollisionShape"
#--------------- presets ----------------------------------
#Objects with -preset in name allow to initialize selected presets (i.e. for RigidBody/StaticBody)
#Example name: "Cube-preset-mask=1024-layers=3-mass=1000"
if (N.name.findn("-preset")>=0): #preset values, ((N.get_type()=="MeshInstance") and )
var matchStart=0
var prevMatchStart=0
var pmatch=presetMatch.search(N.name,matchStart)
while pmatch!=null:
matchStart=pmatch.get_end() #+1
print("preset: "+pmatch.get_string(1)+"<=>"+pmatch.get_string(2)) #+" prevMatchstart: "+str(prevMatchStart)+" matchstart: "+str(matchStart))
if prevMatchStart==matchStart:#check for endless loop
print("error in regexpmatch endless loop")
break
else:
prevMatchStart=matchStart
preset[pmatch.get_string(1)]=pmatch.get_string(2)
delList[N.get_name()]=null
pmatch=presetMatch.search(N.name,matchStart)
#delete child nodes after iteration (to not confuse object iteration)
for i in delList:
delchild(node,i,delList[i])
wombatstampede | 2019-04-16 06:20