Jump to content
Wahooney

It'sa me, Wahooney!

Recommended Posts

Hi all,

My name is Keith, I'm from Cape Town, South Africa. I've been using Unity Professionally for nearly a decade, released a lot of games (99% of which are under NDA :( ). I'm lead programmer at RetroEpic Software; we've released A Day in the Woods and currently on the final stages of the digital edition of Flash Point : Fire Rescue.

I'm super stoked that ProBuilder is now part of the Unity family, and I'd like to get involved in this community. Unity's lack of official level tools has been a pain point of mine for some time.

One of my goals here is to port tools from my own in-Unity modelling system into ProBuilder (API allowing), and if they prove useful maybe make it into the main tool :D As an avid Blender-head, these tools I feel are missing are Blender-inspired and will definitely speed up the level modelling process.

I'll be asking questions here to see if I can make that a reality.

I'll also be complaining very loudly (but in a civil manner) and hopefully not too often about PB whenever there's an improvement to be made, because it's a tool the purpose of which is VERY near to my heart, so I want to make sure it's awesome!

Cheers!

Share this post


Link to post
Share on other sites

Hi Keith! Thanks very much! :)

I don't think we'll be allowed to add in other's work anymore, however there's nothing stopping you from  making "extensions" to ProBuilder and selling them for some sweet Asset Store cash yourself! :D In fact, I'd encourage that!

Looking forward to your feedback, can't make promises, and I do have a personal grudge against Blender, haha, but open to good ideas, always! :)

Share this post


Link to post
Share on other sites

That was plan B ;)

Is it possible to write extensions that create new tools in the tool dock at this point?

Blender is a bit polarizing, for sure, but there are some amazing modelling tools, little things that make a huge difference in productivity, I may change your mind yet ;) 

For instance:

BlenderFaceMaker.gif.e5883ff4dfde7a88db75f339d6c60035.gif

This is just me selecting a single edge and mashing F. Really simple tool, HUGE time saver.

As an ex-Max-Monster, I detect some similarities between it and PB ;) 

Edited by Wahooney
Moar stuff

Share this post


Link to post
Share on other sites
18 hours ago, Wahooney said:

Is it possible to write extensions that create new tools in the tool dock at this point?

Yes, if you're using the Asset Store version of ProBuilder check out the API Examples/Editor folder. If you're using the Package Manager version, here's an example of how to register custom menu actions:

/**
 *	This script demonstrates how to create a new action that can be accessed from the
 *	ProBuilder toolbar.
 *
 *	A new menu item is registered under "Geometry" actions called "Gen. Shadows".
 *
 *	To enable, remove the #if PROBUILDER_API_EXAMPLE and #endif directives.
 */

// Uncomment this line to enable the menu action.
// #define PROBUILDER_API_EXAMPLE

#if PROBUILDER_API_EXAMPLE

using UnityEngine;
using UnityEditor;
using ProBuilder2.Common;
using ProBuilder2.MeshOperations;
using ProBuilder2.EditorCommon;
using ProBuilder2.Interface;
using System.Linq;
using System.Collections.Generic;
using UnityEngine.Rendering;

/**
 *	When creating your own actions please use your own namespace.
 */
namespace ProBuilder2.Actions
{
	/**
	 *	This class is responsible for loading the pb_MenuAction into the toolbar and menu.
	 */
	[InitializeOnLoad]
	static class RegisterCustomAction
	{
		/**
		 *	Static initializer is called when Unity loads the assembly.
		 */
		static RegisterCustomAction()
		{
			// This registers a new CreateShadowObject menu action with the toolbar.
			pb_EditorToolbarLoader.RegisterMenuItem(InitCustomAction);
		}

		/**
		 *	Helper function to load a new menu action object.
		 */
		static pb_MenuAction InitCustomAction()
		{
			return new CreateShadowObject();
		}

		/**
		 *	Usually you'll want to add a menu item entry for your action.
		 * 	https://docs.unity3d.com/ScriptReference/MenuItem.html
		 */
		[MenuItem("Tools/ProBuilder/Object/Create Shadow Object", true)]
		static bool MenuVerifyDoSomethingWithPbObject()
		{
			// Using pb_EditorToolbarLoader.GetInstance keeps MakeFacesDoubleSided as a singleton.
			CreateShadowObject instance = pb_EditorToolbarLoader.GetInstance<CreateShadowObject>();
			return instance != null && instance.IsEnabled();
		}

		[MenuItem("Tools/ProBuilder/Object/Create Shadow Object", false, pb_Constant.MENU_GEOMETRY + 3)]
		static void MenuDoDoSomethingWithPbObject()
		{
			CreateShadowObject instance = pb_EditorToolbarLoader.GetInstance<CreateShadowObject>();

			if(instance != null)
				pb_Notification.Show(instance.DoAction().notification);
		}
	}

	/**
	 *	This is the actual action that will be executed.
	 */
	public class CreateShadowObject : pb_MenuAction
	{
		public override pb_ToolbarGroup group { get { return pb_ToolbarGroup.Object; } }
		public override Texture2D icon { get { return null; } }
		public override pb_TooltipContent tooltip { get { return _tooltip; } }

		private GUIContent gc_volumeSize = new GUIContent("Volume Size", "How far the shadow volume extends from the base mesh.  To visualize, imagine the width of walls.\n\nYou can also select the child ShadowVolume object and turn the Shadow Casting Mode to \"One\" or \"Two\" sided to see the resulting mesh.");

		private bool showPreview
		{
			get { return pb_PreferencesInternal.GetBool("pb_shadowVolumePreview", true); }
			set { pb_PreferencesInternal.SetBool("pb_shadowVolumePreview", value); }
		}

		/**
		 *	What to show in the hover tooltip window.  pb_TooltipContent is similar to GUIContent, with the exception that it also
		 *	includes an optional params[] char list in the constructor to define shortcut keys (ex, CMD_CONTROL, K).
		 */
		static readonly pb_TooltipContent _tooltip = new pb_TooltipContent
		(
			"Gen Shadow Obj",
			"Creates a new ProBuilder mesh child with inverted normals that only exists to cast shadows.  Use to create lit interior scenes with shadows from directional lights.\n\nNote that this exists largely as a workaround for real-time shadow light leaks.  Baked shadows do not require this workaround.",
			""	// Some combination of build settings can cause the compiler to not respection optional params in the pb_TooltipContent c'tor?
		);

		/**
		 *	Determines if the action should be enabled or grayed out.
		 */
		public override bool IsEnabled()
		{
			// `selection` is a helper property on pb_MenuAction that returns a pb_Object[] array from the current selection.
			return 	pb_Editor.instance != null &&
					selection != null &&
					selection.Length > 0;
		}

		/**
		 *	Determines if the action should be loaded in the menu (ex, face actions shouldn't be shown when in vertex editing mode).
		 */
		public override bool IsHidden()
		{
			return pb_Editor.instance == null;
		}

		public override MenuActionState AltState()
		{
			return MenuActionState.VisibleAndEnabled;
		}

		public override void OnSettingsEnable()
		{
			if( showPreview )
				DoAction();
		}

		public override void OnSettingsGUI()
		{
			GUILayout.Label("Create Shadow Volume Options", EditorStyles.boldLabel);

			EditorGUI.BeginChangeCheck();

			EditorGUI.BeginChangeCheck();
			float volumeSize = pb_PreferencesInternal.GetFloat("pb_CreateShadowObject_volumeSize", .07f);
			volumeSize = EditorGUILayout.Slider(gc_volumeSize, volumeSize, 0.001f, 1f);
			if( EditorGUI.EndChangeCheck() )
				pb_PreferencesInternal.SetFloat("pb_CreateShadowObject_volumeSize", volumeSize);

			#if !UNITY_4_6 && !UNITY_4_7
			EditorGUI.BeginChangeCheck();
			ShadowCastingMode shadowMode = (ShadowCastingMode) pb_PreferencesInternal.GetInt("pb_CreateShadowObject_shadowMode", (int) ShadowCastingMode.ShadowsOnly);
			shadowMode = (ShadowCastingMode) EditorGUILayout.EnumPopup("Shadow Casting Mode", shadowMode);
			if(EditorGUI.EndChangeCheck())
				pb_PreferencesInternal.SetInt("pb_CreateShadowObject_shadowMode", (int) shadowMode);
			#endif

			EditorGUI.BeginChangeCheck();
			ExtrudeMethod extrudeMethod = (ExtrudeMethod) pb_PreferencesInternal.GetInt("pb_CreateShadowObject_extrudeMethod", (int) ExtrudeMethod.FaceNormal);
			extrudeMethod = (ExtrudeMethod) EditorGUILayout.EnumPopup("Extrude Method", extrudeMethod);
			if(EditorGUI.EndChangeCheck())
				pb_PreferencesInternal.SetInt("pb_CreateShadowObject_extrudeMethod", (int) extrudeMethod);

			if(EditorGUI.EndChangeCheck())
				DoAction();

			GUILayout.FlexibleSpace();

			if(GUILayout.Button("Create Shadow Volume"))
			{
				DoAction();
				SceneView.RepaintAll();
				pb_MenuOption.CloseAll();
			}
		}

		/**
		 *	Do the thing.  Return a pb_ActionResult indicating the success/failure of action.
		 */
		public override pb_ActionResult DoAction()
		{
			#if !UNITY_4_6 && !UNITY_4_7
			ShadowCastingMode shadowMode = (ShadowCastingMode) pb_PreferencesInternal.GetInt("pb_CreateShadowObject_shadowMode", (int) ShadowCastingMode.ShadowsOnly);
			#endif
			float extrudeDistance = pb_PreferencesInternal.GetFloat("pb_CreateShadowObject_volumeSize", .08f);
			ExtrudeMethod extrudeMethod = (ExtrudeMethod) pb_PreferencesInternal.GetInt("pb_CreateShadowObject_extrudeMethod", (int) ExtrudeMethod.FaceNormal);

			foreach(pb_Object pb in selection)
			{
				pb_Object shadow = GetShadowObject(pb);

				if(shadow == null)
					continue;

				foreach(pb_Face f in shadow.faces) { f.ReverseIndices(); f.manualUV = true; }
				shadow.Extrude(shadow.faces, extrudeMethod, extrudeDistance);
				shadow.ToMesh();
				shadow.Refresh();
				shadow.Optimize();

				#if !UNITY_4_6 && !UNITY_4_7
				MeshRenderer mr = shadow.gameObject.GetComponent<MeshRenderer>();
				mr.shadowCastingMode = shadowMode;
				if(shadowMode == ShadowCastingMode.ShadowsOnly)
					mr.receiveShadows = false;
				#endif

				Collider collider = shadow.GetComponent<Collider>();

				while(collider != null)
				{
					GameObject.DestroyImmediate(collider);
					collider = shadow.GetComponent<Collider>();
				}
			}

			// This is necessary!  Otherwise the pb_Editor will be working with caches from
			// outdated meshes and throw errors.
			pb_Editor.Refresh();

			return new pb_ActionResult(Status.Success, "Create Shadow Object");
		}

		private pb_Object GetShadowObject(pb_Object pb)
		{
			if(pb == null || pb.name.Contains("-ShadowVolume"))
				return null;

			for(int i = 0; i < pb.transform.childCount; i++)
			{
				Transform t = pb.transform.GetChild(i);

				if(t.name.Equals(string.Format("{0}-ShadowVolume", pb.name)))
				{
					pb_Object shadow = t.GetComponent<pb_Object>();

					if(shadow != null)
					{
						pbUndo.RecordObject(shadow, "Update Shadow Object");

						pb_Face[] faces = new pb_Face[pb.faces.Length];

						for(int nn = 0; nn < pb.faces.Length; nn++)
							faces[nn] = new pb_Face(pb.faces[nn]);

						shadow.GeometryWithVerticesFaces(pb.vertices, faces);
						return shadow;
					}
				}
			}

			pb_Object new_shadow = pb_Object.InitWithObject(pb);
			new_shadow.name = string.Format("{0}-ShadowVolume", pb.name);
			new_shadow.MakeUnique();
			new_shadow.transform.SetParent(pb.transform, false);
			Undo.RegisterCreatedObjectUndo(new_shadow.gameObject, "Create Shadow Object");
			return new_shadow;
		}
	}
}
#endif

 

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×