├── .gitignore ├── LICENSE ├── README.md └── __init__.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | venv 3 | lib -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 RubielGames 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2D/2.5D Sprite Renderer for Blender 3.x 2 | 3 | The 2D/2.5D Sprite Renderer is a Blender addon designed to render 3D models from many angles. It's ideal for creating 2D sprites for 2D and 2.5D games. 4 | 5 | ## Installation 6 | 1. Download the addon from GitHub as a .zip file (click "Code" then "Download ZIP"). 7 | 2. In Blender, go to Edit > Preferences. 8 | 3. In the Addons tab, select Install... to open a file dialog. 9 | 4. Find and select the .zip file you downloaded, then click Install Add-on. 10 | 5. The addon should now be in the list of addons. Enable it by checking the box next to its name. 11 | 12 | ## Usage 13 | 1. Open the 3D view sidebar (press N if hidden), and find the "Sprite Renderer" panel. 14 | 2. Set rendering parameters: 15 | * `Camera`: Choose a camera for rendering. Leave empty to use camera(s) parameters. 16 | * `Output Folder`: Directory for saving rendered images. 17 | * `Basename`: Base name for image files. 18 | * `Include Animation`: Enable to render animations. 19 | * `Frame Step`: If using animation, define the frame step for rendering. 20 | * `Character Height`: Set the character's height in the scene. 21 | * `Number of Angles`: Define how many angles to render the character from. 22 | * `Perspective`: Choose 2D or 2.5D. In 2.5D, set the camera angle. 23 | * `Camera Angle`: Set the camera angle for 2.5D perspective. 24 | * `Camera Type`: Choose Orthographic or Perspective. 25 | * `Resolution`: Set the output render resolution. 26 | 3. Click Render Sprites to start. This creates a sprite image for each angle in the output directory. 27 | 28 | ## Usage - No Camera Selected 29 | This is the default setting. Ensure the character is at the origin (`0, 0, 0`). The addon will position cameras around the character based on your parameters. Typical top-down game settings are a `camera angle` of 30 to 60, `perspective` at 2.5, and `camera type` as Orthographic. 30 | 31 | ## Usage - Your Own Camera 32 | This will disable Camera Settings box. Animation is still usable, ideal for rendering effects or non-static movements. For a camera style like the default: 33 | * Select your camera. 34 | * Go to the `Data` tab, expand `Lens`, and set `Type` to Orthographic. 35 | * In the `Output` tab, adjust `Resolution` in the Format box. 36 | * Press `F12` to test rendering. 37 | 38 | ## Tested on Versions 39 | * Blender 4.0 40 | * Blender 3.6 41 | * GooEngine 3.6 42 | * Blender 3.4 43 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name": "2D/2.5D Sprite Renderer", 3 | "description": "Automatically renders a character from multiple angles for use in 2D or 2.5D games", 4 | "author": "Rubiel", 5 | "version": (1, 1), 6 | "blender": (3, 4, 0), 7 | "location": "View3D > Sidebar > Sprite Renderer Panel", 8 | "category": "Object", 9 | } 10 | 11 | import bpy 12 | import os 13 | from math import cos, sin, tan, atan 14 | from mathutils import Vector 15 | 16 | 17 | class CharacterSpriteRenderer(bpy.types.Operator): 18 | """2D/2.5D Sprite Renderer""" # Use this as a tooltip for menu items and buttons. 19 | bl_idname = "object.sprite_renderer" # Unique identifier for buttons and menu items to reference. 20 | bl_label = "Render Sprites" # Display name in the interface. 21 | 22 | def execute(self, context): 23 | 24 | # Check if the camera path is valid 25 | camera_path = context.scene.sprite_renderer.camera_path 26 | output_folder = bpy.path.abspath(context.scene.sprite_renderer.output_folder) 27 | os.makedirs(output_folder, exist_ok=True) 28 | basename = context.scene.sprite_renderer.basename 29 | scene = bpy.context.scene 30 | include_animation = context.scene.sprite_renderer.include_animation 31 | frame_step = context.scene.sprite_renderer.frame_step 32 | # Get the frame range of the animation 33 | frame_start = scene.frame_start 34 | frame_end = scene.frame_end 35 | 36 | 37 | if camera_path == "": 38 | # Getting properties from the scene 39 | character_position = Vector((0, 0, 0)) 40 | character_height = context.scene.sprite_renderer.character_height 41 | number_of_angles = context.scene.sprite_renderer.number_of_angles 42 | 43 | scene.render.resolution_x = int(context.scene.sprite_renderer.resolution) 44 | scene.render.resolution_y = int(context.scene.sprite_renderer.resolution) 45 | scene.render.film_transparent = True 46 | 47 | perspective = context.scene.sprite_renderer.perspective 48 | camera_type = context.scene.sprite_renderer.camera_type 49 | 50 | # Check if the camera already exists in the scene 51 | camera_name = '2DSpriteCamera' 52 | if camera_name in bpy.data.objects: 53 | camera = bpy.data.objects[camera_name] 54 | else: 55 | # Add a new camera to the scene 56 | bpy.ops.object.camera_add(location=character_position) 57 | camera = bpy.context.object # the newly added camera becomes the active object 58 | camera.name = camera_name # set the camera name 59 | scene.camera = camera 60 | 61 | if camera_type == 'ORTHO': 62 | camera.data.type = 'ORTHO' 63 | camera.data.ortho_scale = character_height * 1.1 64 | else: 65 | camera.data.type = 'PERSP' 66 | 67 | if perspective == '2D': 68 | distance = 10 69 | z_distance = 0 70 | else: 71 | distance = 0.5 * character_height / tan(0.5 * atan(0.5 * camera.data.sensor_height / camera.data.lens)) 72 | camera_angle_rad = context.scene.sprite_renderer.camera_angle * 3.14159 / 180 # converting to radians 73 | z_distance = distance * tan(camera_angle_rad) 74 | 75 | # Loop to set the camera's position and render the scene 76 | for i in range(number_of_angles): 77 | angle = i * (2.0 * 3.14159 / number_of_angles) # calculate angle 78 | 79 | # Set the camera's position 80 | camera.location.x = character_position.x + distance * cos(angle) 81 | camera.location.y = character_position.y + distance * sin(angle) 82 | camera.location.z = character_position.z + (character_height / 2) + z_distance 83 | 84 | # Point the camera to the character 85 | direction = Vector((character_position.x, character_position.y, character_position.z + (character_height / 2))) - camera.location 86 | camera.rotation_mode = 'QUATERNION' 87 | camera.rotation_quaternion = direction.to_track_quat('-Z', 'Y') # camera looks towards the character 88 | 89 | # Check if there is animation 90 | if include_animation and frame_start != frame_end: 91 | # Loop through every frame of the animation 92 | for frame in range(frame_start, frame_end + 1, frame_step): 93 | scene.frame_set(frame) 94 | # Set output path 95 | scene.render.filepath = os.path.join(output_folder, '{}_angle{}_frame{:03}.png'.format(basename, i, frame)) 96 | # Render the scene 97 | bpy.ops.render.render(write_still = True) 98 | else: 99 | # Handle static scene (no animation) 100 | scene.render.filepath = os.path.join(output_folder, '{}_angle{}.png'.format(basename, i)) 101 | # Render the scene 102 | bpy.ops.render.render(write_still = True) 103 | 104 | bpy.data.objects.remove(camera, do_unlink=True) 105 | else: 106 | scene.camera = bpy.data.objects[camera_path] 107 | if include_animation and frame_start != frame_end: 108 | # Loop through every frame of the animation 109 | for frame in range(frame_start, frame_end + 1, frame_step): 110 | scene.frame_set(frame) 111 | scene.render.filepath = os.path.join(output_folder, '{}_frame{:03}.png'.format(basename, frame)) 112 | bpy.ops.render.render(write_still = True) 113 | else: 114 | # Handle static scene (no animation) 115 | scene.render.filepath = os.path.join(output_folder, '{}.png'.format(basename)) 116 | # Render the scene 117 | bpy.ops.render.render(write_still = True) 118 | 119 | return {'FINISHED'} # Lets Blender know the operator finished successfully. 120 | 121 | 122 | # This is the Panel where you set the properties 123 | class SpriteRendererPanel(bpy.types.Panel): 124 | """Creates a Panel in the Object properties window""" 125 | bl_label = "Sprite Renderer" 126 | bl_idname = "OBJECT_PT_sprite_renderer" 127 | bl_space_type = 'VIEW_3D' 128 | bl_region_type = 'UI' 129 | bl_category = "Sprite Renderer" 130 | 131 | def draw(self, context): 132 | layout = self.layout 133 | 134 | scene = context.scene 135 | renderer = scene.sprite_renderer 136 | 137 | layout.prop(renderer, "camera_path") 138 | # draw the properties 139 | layout.prop(renderer, "output_folder") 140 | layout.prop(renderer, "basename") 141 | 142 | layout.prop(renderer, "include_animation") 143 | # Only enable frame_step if include_animation is True 144 | row = layout.row() 145 | row.enabled = renderer.include_animation 146 | row.prop(renderer, "frame_step") 147 | 148 | # Camera settings subsection 149 | camera_box = layout.box() 150 | # Disable the entire camera settings box if camera_path is not empty 151 | camera_box.enabled = (renderer.camera_path == "") 152 | 153 | # Adding a label for the subsection 154 | camera_box.label(text="Camera Settings") 155 | 156 | camera_box.prop(renderer, "character_height") 157 | camera_box.prop(renderer, "number_of_angles") 158 | 159 | camera_box.prop(renderer, "perspective") 160 | 161 | row = camera_box.row() 162 | row.enabled = (renderer.perspective == '2.5D') 163 | row.prop(renderer, "camera_angle") 164 | 165 | camera_box.prop(renderer, "camera_type") 166 | camera_box.prop(renderer, "resolution") 167 | 168 | # draw the operator 169 | layout.operator("object.sprite_renderer") 170 | 171 | 172 | # This is the property group where you store your variables 173 | class SpriteRendererProperties(bpy.types.PropertyGroup): 174 | 175 | camera_path: bpy.props.StringProperty(name="Camera", description="Path to an existing camera, if none is provided a new one will be created", default="") 176 | output_folder: bpy.props.StringProperty(name="Output Folder", default="//", subtype='DIR_PATH') 177 | basename: bpy.props.StringProperty(name="Basename", default="render") 178 | 179 | include_animation: bpy.props.BoolProperty( 180 | name="Include Animation", 181 | default=False, 182 | description="Whether to render all frames of the animation for each angle.", 183 | ) 184 | frame_step: bpy.props.IntProperty( 185 | name="Frame Step", 186 | default=1, 187 | min=1, 188 | description="Render every n-th frame of the animation. For example, if set to 3, only every third frame will be rendered.", 189 | ) 190 | 191 | character_height: bpy.props.FloatProperty(name="Character Height", default=2.0) 192 | number_of_angles: bpy.props.IntProperty(name="Number of Angles", default=8) 193 | perspective: bpy.props.EnumProperty( 194 | name="Perspective", 195 | description="Select Perspective type", 196 | items=[ 197 | ('2D', "2D", ""), 198 | ('2.5D', "2.5D", "") 199 | ], 200 | default='2.5D', 201 | ) 202 | camera_angle: bpy.props.FloatProperty(name="Camera Angle", default=30.0, min=0.0, max=89.0) 203 | camera_type: bpy.props.EnumProperty( 204 | name="Camera Type", 205 | description="Select Camera type", 206 | items=[ 207 | ('ORTHO', "Orthographic", ""), 208 | ('PERSP', "Perspective", "") 209 | ], 210 | default='ORTHO', 211 | ) 212 | resolution: bpy.props.EnumProperty( 213 | name="Resolution", 214 | description="Select the resolution", 215 | items=[ 216 | ('32', "32x32", ""), 217 | ('64', "64x64", ""), 218 | ('128', "128x128", ""), 219 | ('256', "256x256", ""), 220 | ('512', "512x512", "") 221 | ], 222 | default='512', 223 | ) 224 | 225 | 226 | # Registration 227 | def register(): 228 | bpy.utils.register_class(CharacterSpriteRenderer) 229 | bpy.utils.register_class(SpriteRendererPanel) 230 | bpy.utils.register_class(SpriteRendererProperties) 231 | bpy.types.Scene.sprite_renderer = bpy.props.PointerProperty(type=SpriteRendererProperties) 232 | 233 | 234 | def unregister(): 235 | bpy.utils.unregister_class(CharacterSpriteRenderer) 236 | bpy.utils.unregister_class(SpriteRendererPanel) 237 | bpy.utils.unregister_class(SpriteRendererProperties) 238 | del bpy.types.Scene.sprite_renderer 239 | 240 | if __name__ == "__main__": 241 | register() --------------------------------------------------------------------------------