If you stumbled upon this, more likely than not, you want to create a deformable mesh ocean like a lot of these cutting edge games nowadays. Good news is that after days of my own searching an learning to no avail, I eventually figured it out and now I’ll share what my process was.
Note: This tutorial only covers the setup, the mesh, and the basics to scripting the waves. If you want an in depth tutorial on the wave equation and scripting side of things, use this well written post.
This is my first tutorial
Ok,
So your first step is making a plane with the resolution of the ocean you desire. If you know how to use blender, this is easy. Otherwise just add a plane object and subdivide it several times. This has the potential to really destroy performance so you’re riding a balance between how good your water looks and how well the game runs.
8k tris worked fine for me, but its straddling the edge of what is performant in my case.
Next what you want to do is give each vertex a bone. Open blender's .py text window and paste this code. It adds a bone to each vertex location and rigs the vertex to it.
import bpy
from mathutils import Vector
def AddBonesAtVertices(obj, length, use_normals):
if not obj or obj.type != 'MESH':
return
points = []
normals = []
data = []
for v in obj.data.vertices:
p = obj.matrix_world @ v.co
target = v.normal @ obj.matrix_world
dir = target - p
dir.normalize()
dir = dir * length
n = p + dir * (-1)
points.append(p)
if not use_normals:
n = Vector((p[0], p[1], p[2] + length))
normals.append(n)
data.append([p, n])
bpy.ops.object.mode_set(mode='OBJECT')
amt = bpy.data.armatures.new(obj.name + "_vBones")
rig = bpy.data.objects.new(obj.name + '_vRig', amt)
bpy.context.collection.objects.link(rig)
objects.active = rig
names = [] #Will keep bone names
bpy.ops.object.editmode_toggle()
for i, l in enumerate(zip(points, normals)):
bone = amt.edit_bones.new(str(i))
bone.head = l[0]
bone.tail = l[1]
names.append(bone.name) #Add name
bpy.ops.object.editmode_toggle()
for v_index, name in enumerate(names):
#Get the group
group = obj.vertex_groups.new(name=name)
#Link the vertex to it
group.add([v_index], 1, 'REPLACE')
#Parent and add modifier
obj.parent = rig
modifier = obj.modifiers.new(rig.name, "ARMATURE")
modifier.object = rig
objects = bpy.context.view_layer.objects
obj = objects.active
AddBonesAtVertices(obj, 0.5, False)
It may take a while to run.
If you plan to have your ocean textured, now is a good time to edit the UV of the plane and scale it up so more repetitions of whichever texture you use will be applied, and the smaller/clearer the textures become.
Next, you're going to Export as FBX. and put that off to the side for now. Open roblox studio and import an avatar using the avatar importer plugin. That way the bones are sorted out for you. Pick Custom.
Upload the fbx you set aside and it will enter your workspace
Make sure everything is there and rigged correctly. All 4k of my bones were there and the mesh deformed when I moved them.
Phase 2 is animation. There’s many ways to accomplish this, but you don’t actually have to use the animator at this point. I accomplished my effect by looping through all of the bones and applying a Gerstner Wave fomula. I won’t share my code because its very inefficient and the formula was made incorrectly, so it would be no help, but I think this is a good start to get you where you need to be.
My final outcome. I used the same formula to calculate where the floating objects should be. Real simple since it takes an X and a Z value and spits out what the Y value should be when taking into account a "C" value which is time. I put that Y value in a body position.
Some caveats to be aware of:
- If you do use a wave formula like I suggested, you will need to run it on the client or it will be jittery and slow the server down.
- If its run on the client, you will need to synchronize “C” (time) be the same between all clients. This can be done with a client/server clock synchonizer. A quick search will return a couple options.
- Since C is just a variable like tick() or os.time() (hopefully synchronized at this point), you can counter lag or travel-time for body positions by consistently adding or subtracting a number from C. C+/-1 for instance.
- Despite being a meshpart, this water unfortunately DOES NOT have collisions. It acts similarly to a special mesh in that it doesn’t react to raycasting or collisions.
Ocean Mesh
I hope this helped.