Make sure you’ve already done at least:
Let’s get started…
What can you do? Why bother?
Procedural levels / auto-generation
Your snap-sockets already hold all the info needed to control level-layout.
So … with only a small amount of code, you can get Unity and SnapAndPlug to auto-create levels for you. Procedural generation made easy!
(this is why I created it in the first place: to help me make Skyrim-style dungeons, only with more flexibility. c.f. Joel Burgess’s GDC talk on the internal tools they used to make modular dungeons for Skyrim)
Custom socket-connections, custom rendering
Whenever you make a socket in SnapAndPlug, it’s always drawn as a Square.
That’s great! … if your connections are naturally square.
But wouldn’t it be nice if you could make the render fit the actual shape of your (custom) doorways / connections / etc?
Even better: for different connection-types (c.f. Tutorial 3: Advanced Snapping), you could draw each one differently, making it ultra easy to see which is which when editing a large level.
Well … yeah. You can.
Internal classes
The following API’s are subject to change! I will try to only ADD to them, never CHANGE OR REMOVE. The main API calls are stable, but some of the less-used ones might need to change at some point.
SnapSocket
public Vector3 outVector public Vector3 upVector public Quaternion CalculateOutQuaternion()
…these give you the directions for a vector OUT of the socket (outVector), and UPWARDS FROM the floor (upVector). The second one is needed so that we can create Quaternions – and make our rotations work correctly when rotating objects together for snapping! The “Calculate” method auto-creates that quaternion for you, since it’s used in a lot of places.
public SnapPiece roomCurrent;
…the parent SnapPiece that contains this SnapSocket. Currently: this is always “transform.parent.gameObject.getComponent
public bool isConnected public SnapSocket socketConnectedTo;
…if the socket is connected to another socket, this tells you, and gives you the “other” socket.
public float diameterApproximately;
…diameter in world-units used to render the previews of this socket in Scene View.
public string connectionType; public bool CanConnectTo (SnapSocket other);
…Sockets can ONLY connect to other sockets with the same “connectionType” (c.f. Tutorial 3: Advanced Snapping). This method lets you check that without worrying about string compare (NB: please use this method rather than string compare – the rules for snapping may change in future, but this method is what’s used internally everywhere).
public void ConnectWithoutMoving (SnapSocket otherSocket); public void DisconnectWithoutMoving(); public bool ConnectByMovingToJoinDoorway (SnapSocket stationaryDoor);
…connecting and disconnecting. The ones “WithoutMoving” are used internally AFTER a SnapPiece / SnapGroup has been moved into position and rotation – they do the final “connect”.
Generally, we only ever use the last one: this has all the smart code for doing snapping, moving, rotating, etc.
SnapPiece
public List<SnapSocket> sockets public SnapGroup group
…the sockets for this Piece, and the parent Group (if it has one).
public int CountUnconnectedSockets() public int CountTotalSockets() public List<SnapSocket> GetAllUnconnectedSockets() public List<SnapSocket> GetAllConnectedSockets() public SnapSocket GetFirstUnconnectedSocket() public SnapSocket GetRandomAvailableSocket( SnapSocket otherSocket ) public SnapSocket getRandomUnconnectedExit()
…a whole bunch of utility methods for getting different sub-slices of the “sockets” List above.
GetRandomAvailableSocket(..) is particularly useful – it uses the SnapSocket’s internal “CanConnectTo” methods to find you only valid matches – or null if it can’t find any that match the incoming Socket.
SnapGroup
(none yet – the methods on this class are currently subject to major change)
Custom rendering
Core classes
SnapCustomMold: your custom renderer MUST subclass this.
When rendering, SnapAndPlug looks for “Molds” to do the actual work of drawing.
Subclassing SnapCustomMold
Your custom mold has to do four things:
- SubClass SnapCustomMold
- Tell C# which kind of connection-type it’s for
- (optional, but recommended) Implement the PixelDistance method
- Implement one or more of the rendering methods to draw itself
Your class signature should look like this:
using UnityEngine; using UnityEditor; using System.Collections; using SnapAndPlug; using SnapAndPlugEditorScripts; [SnapCustomMold("Doorway")] public class MyCustomMoldForDoorways : SnapCustomMold {
Note: when rendering, SnapAndPlug will use the “connection type” on the SnapSocket to decide which mold to draw with.
Implement the PixelDistance method
This method:
public override float PixelDistanceFromMouseToHandle (Vector3 moldCenter, Quaternion moldOutDirection, float moldRadius)
is called on your custom mold when the user clicks the mouse and starts dragging. It tells Unity whether or not they’ve actually clicked the socket – or if they missed.
If you DON’T override this method, the hot-area for mouse clicks will be the default (a square with diameter equal to the value in the SnapSocket inspector).
Unity gives us some convenience methods for working this out. For instance, here’s the default implementation we use for the Square sockets:
public override float PixelDistanceFromMouseToHandle (Vector3 moldCenter, Quaternion moldOutDirection, float moldRadius) { return HandleUtility.DistanceToRectangle ( moldCenter, moldOutDirection, moldRadius ); }
…check out Unity’s HandleUtility class for more options and information.
Implement the rendering methods
There are two methods used when drawing molds for sockets:
public override void DrawMoldUnconnected( SnapSocket socket ) public override void DrawMoldWhileUserIsDraggingHandle( SnapSocket socket, int unityControlID )
Unsurprisingly, one of these is called when the socket is just sitting there, disconnected (this by default draws a big solid red square), and the other draws it while the user is dragging the socket and trying to snap it somewhere.
The second one has an extra argument – the ControlID provided by Unity. You can ignore this if you like, but some of the Unity methods you might want to call at this point will require it as an argument.
There are some useful methods on SnapSocket itself that you might want to use – see above for more info.
Here’s the default implementation (both methods draw the same thing by default) :
public override void DrawMoldUnconnected( SnapSocket socket ) { /** abuse DrawSolidRectangleWithOutline to draw a square and a triangle. Muahahaha...*/ Vector3[] doorwaySquareVertices1 = new Vector3[4]; Vector3[] doorwaySquareVertices2 = new Vector3[4]; float f = socket.diameterApproximately / 2f; Vector3 doorwayShapeOrigin = socket.attachPoint; float heightOfTickShowingUp = 0.3f * f; Quaternion outQuat = socket.CalculateOutQuaternion(); doorwaySquareVertices1 [0] = doorwayShapeOrigin + outQuat * new Vector3(-1f * f, -1f * f, 0); doorwaySquareVertices1 [1] = doorwayShapeOrigin + outQuat * new Vector3(-1f * f, 1f * f, 0); doorwaySquareVertices1 [2] = doorwayShapeOrigin + outQuat * new Vector3(1f * f, 1f * f, 0); doorwaySquareVertices1 [3] = doorwayShapeOrigin + outQuat * new Vector3(1f * f, -1f * f, 0); doorwaySquareVertices2 [0] = doorwayShapeOrigin + outQuat * new Vector3(-heightOfTickShowingUp, f, 0); doorwaySquareVertices2 [1] = doorwayShapeOrigin + outQuat * new Vector3(0, f + heightOfTickShowingUp, 0); doorwaySquareVertices2 [2] = doorwayShapeOrigin + outQuat * new Vector3(heightOfTickShowingUp, f, 0); doorwaySquareVertices2 [3] = doorwayShapeOrigin + outQuat * new Vector3(0f, f, 0); /* NB: bug in Unity 4.x: DrawSolidRectangleWithOutline ignores colour! */ Handles.DrawSolidRectangleWithOutline(doorwaySquareVertices1, Color.white, Color.blue); Handles.DrawSolidRectangleWithOutline(doorwaySquareVertices2, Color.white, Color.blue); }
Procedural level generation
Here’s code for a very simple auto-generator. I’m providing this code “as-is” – if it’s not obvious how to use it, I suggest you google “Maze Generation”, and “Dungeon generation” algorithms – or go to Reddit’s /r/gamedev/ and ask for advice.
public SnapPiece[] prefabSnapPieces; // add a bunch of prefabs to this, each of which has a single SnapPiece you configured
public void CreateAndAddRoomsToRoom (SnapPiece baseRoom, int maxDepth, int currentDepth = 0)
{
//Debug.Log( “Creating rooms at depth: “+currentDepth+ “/” +maxDepth );
List
int numNewAtThisLevel = baseRoom.GetAllUnconnectedSockets ().Count;
foreach (SnapSocket unconnectedSocket in baseRoom.GetAllUnconnectedSockets())
{
RoomTraversability traverse;
GameObject prefabNextRoom = null;
prefabNextRoom = prefabSnapPieces[(int)Random.Range (0, prefabSnapPieces.Length)];
GameObject nextRoomGO = GameObject.Instantiate (prefabNextRoom) as GameObject;
SnapPiece nextRoom = nextRoomGO.GetComponent
newlyAddedRooms.Add (nextRoom);
SnapSocket socketNextToPrevious = nextRoom.getRandomUnconnectedExit ();
socketNextToPrevious.ConnectByMovingToJoinDoorway (unconnectedSocket, true);
}
if (currentDepth < maxDepth) { /** Do this as a second phase, so that we build our tree BREADTH first */ foreach (SnapPiece newRoom in newlyAddedRooms) { CreateAndAddRoomsToRoom (newRoom, maxDepth, currentDepth + 1); } } } [/csharp]