Blender to Roblox Camera Cutscene Addon

Camera Cutscene Addon


This addon allows you to export Blender camera keyframes to JSON data, which then can be used to make Roblox cutscenes after decoding to Lua.

Installing Addon to Blender

Step 1

Create a Python script file with the Addon source code.

Source Code:
bl_info = {
    "name": "Export Camera Animation to JSON (with CFrame Matrix)",
    "author": "ApparentlyJames",
    "version": (1, 2),
    "blender": (2, 80, 0),
    "location": "File > Export > Camera Animation (.json)",
    "description": "Export camera animation with baked modifiers using CFrame-compatible matrix format for Roblox.",
    "category": "Import-Export",
}

import bpy
import json
from bpy_extras.io_utils import ExportHelper
import mathutils
import bpy_extras

class ExportCameraAnimation(bpy.types.Operator, ExportHelper):
    bl_idname = "export_anim.camera_json"
    bl_label = "Export Camera Animation"
    filename_ext = ".json"

    filter_glob: bpy.props.StringProperty(default="*.json", options={'HIDDEN'})

    def execute(self, context):
        scene = context.scene
        cam = scene.camera

        if cam is None:
            self.report({'ERROR'}, "No active camera in the scene.")
            return {'CANCELLED'}

        depsgraph = context.evaluated_depsgraph_get()
        eval_cam = cam.evaluated_get(depsgraph)

        fps = scene.render.fps
        start = scene.frame_start
        end = scene.frame_end

        transform_to_y_up = bpy_extras.io_utils.axis_conversion(
            from_forward='-Y', from_up='Z', to_forward='Z', to_up='Y'
        ).to_4x4()

        cframe_frames = {}
        fov_frames = {}

        for frame in range(start, end + 1):
            scene.frame_set(frame)
            depsgraph.update()

            matrix = eval_cam.matrix_world.copy()
            y_up_matrix = transform_to_y_up @ matrix

            cframe_components = [
                y_up_matrix[0][3], y_up_matrix[1][3], y_up_matrix[2][3],
                y_up_matrix[0][0], y_up_matrix[0][1], y_up_matrix[0][2],
                y_up_matrix[1][0], y_up_matrix[1][1], y_up_matrix[1][2],
                y_up_matrix[2][0], y_up_matrix[2][1], y_up_matrix[2][2],
            ]

            cframe_frames[frame] = cframe_components
            fov_frames[frame] = cam.data.angle

        output = {
            "camera_animation": {
                "fps": fps,
                "frame_range": [start, end],
                "frames": cframe_frames,
                "fov_frames": fov_frames
            }
        }

        with open(self.filepath, 'w') as f:
            json.dump(output, f, indent=4)

        self.report({'INFO'}, f"Camera animation exported to {self.filepath}")
        return {'FINISHED'}

def menu_func_export(self, context):
    self.layout.operator(ExportCameraAnimation.bl_idname, text="Camera Animation (.json)")

def register():
    bpy.utils.register_class(ExportCameraAnimation)
    bpy.types.TOPBAR_MT_file_export.append(menu_func_export)

def unregister():
    bpy.utils.unregister_class(ExportCameraAnimation)
    bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)

if __name__ == "__main__":
    register()

Installing Roblox Plugins

Cutscene Essentials - Creator Store
JSON Importer - Creator Store

Usage Example
Basic Animating

Step 0 (optional):

Enable Auto-Keying by pressing the dot button on the timeline.

image

Step 1:

Set Viewpoint to be the camera by going to View > Viewpoint > Camera, or alternatively pressing the keybind assigned to it (Numpad 0).

Step 2:

Go to Fly/Walk Navigation to position the Camera to your needs by going to View > Navigation > Move Navigation, or alternatively pressing the keybind assigned to it (Shift + F).

Step 3:

Once you're done with the first keyframe, expand the "Summary" in the timeline and make sure the keyframe is inserted. If not, then press I to insert it manually.

Step 4 (optional):

Uncheck "Only Keyframes from Selected Channels", so the timeline keyframes are always visible.

This concludes the essential basic animating, for advanced camera manipulation you should research from YouTube or other articles.

How to Use the Camera in Blender (Tutorial) - YouTube
Mastering Camera Control in Blender: A Comprehensive Guide - BlinksAndButtons

Adding scene to Blender

Step 1:

Select the instances you'll want to insert to Blender, then Right-click and press "Export Selection".

Step 2:

Create a folder, and export selection into it.

Step 3:

On Blender, go to File > Import > Wavefront (.obj), and go to the folder you just created, then select the .obj file.

Step 4:

!! DO NOT MOVE OR SCALE THE IMPORTED SCENE !!

Use the Walk Navigation to move to the scene and continue your animating process.

Tip: Hold SHIFT to move faster.

Exporting

Before baking and exporting, I recommend saving the .blend file to a folder, incase you'll want to edit something.

Step 1:

Press F3, and type in "bake". The Bake Actions, so if you have any graph modifiers, they should get baked onto the animation timeline.

Step 2:

Go to File > Export > Camera Animation (.json). This will save your camera keyframes as a JSON file.

Previewing Animation

Make sure you have installed both Roblox Plugins listed above

Step 1:

Use the JSON Importer plugin to import the exported animation data to workspace.

image

Step 2:

Select the module script you just imported, and click "Play Camera Keyframes"

Code Example to playing animations in-game
-- Function to turn Array keyframe to a CFrame
local function arrayToCFrame(arr)
	return CFrame.new(
		arr[1], arr[2], arr[3],
		arr[4], arr[5], arr[6],
		arr[7], arr[8], arr[9],
		arr[10], arr[11], arr[12]
	)
end

-- Animation Variables
local AnimationModule = require(pathToModule)
local animation = AnimationModule.camera_animation

local startPoint = animation.frame_range[1]
local endPoint = animation.frame_range[2]

-- Keyframe Loop
for i = startPoint, endPoint do
	local keyframeID = tostring(i)
	local keyframe = animation.frames[keyframeID]

	if not keyframe then
		continue
	end

	TweenService:Create(workspace.Camera, TweenInfo.new(1 / animation.fps, Enum.EasingStyle.Linear), {
		CFrame = arrayToCFrame(keyframe),
		FieldOfView = math.deg(animation.fov_frames[keyframeID])
	}):Play()

	task.wait(1 / animation.fps)
end

--[[
Animation Data  Format:
{
	camera_animation = {
		fps = <number>,
		frame_range = {
			<start_keyframe_number>,
			<end_keyframe_number>
		},
		frames = {
			[<keyframe_id_string>] = <cframe_array>,
			...
		},
		fov_frames = {
			[<keyframe_id_string>] = <fov_radians>,
			...
		},
	}
}
]]

If you like the plugin, consider donating (through the gamepasses) :]

Made with :heart: by ApparentlyJames


6 Likes