Creating a Drag and Drop Spell Bar

From Unify Community Wiki
Revision as of 18:34, 19 September 2010 by Fishypants (Talk | contribs)

Jump to: navigation, search

Implementing a Drag and Drop Spell Bar:
by Mike Cook.



I am sure this has been covered somewhere in the Unity Forums, but I couldn't find any relevant information when I was starting out on this, so hopefully this tutorial will help someone starting out with Unity. Basically we will be mimicking the spell bar seen in such games as World of Warcraft, or Aion.

Step 1: Plan what you want to do before you do it

I have a very bad habit of "rushing" into things. I get an idea, and I HAVE TO IMPLEMENT IT THAT VERY SECOND. So I quickly slap things together in my main script and it usually results in a mess. I can't stress enough the fact that careful planning will go a long way. For our custom drag and drop spell bar, we want to implement the following features:

  • Easily add items to the spell bar.
  • Drag and drop items off spell bar to delete them.
  • Drag and drop item into empty slot to switch its location.
  • Drag and drop item into occupied slot to swap the items.
  • Restrict certain items from going into certain slots.
  • Drag and drop feature will be "sticky" meaning the user will have to drag a ways for the icon to disengage from the slot.
  • Clicking an item will activate it, or use it.
  • Right clicking while dragging will cancel the currently dragged item.

Unity out of the box does not support any button that will accomplish what we want to do (that I am aware of). The default GUI.Button only registers when you release the button, and the Repeat Button only registers when the user is holding the mouse down while above the button. It became clear when I first started this project that I would have to implement my own buttons and ditch Unity's all together.

We actually only need to know 2 things to implement this drag and drop system:

  • Where the mouse is when the user presses the mouse button down.
  • Where the mouse is when the user releases the mouse button.

Thats pretty much it. It will get a little more complicated then that, but those are the key points to get the drag and drop system to work. One of the hardest parts of this script was "knowing where the mouse is" at all times. I struggled with different ways to loop through different Rect's and try to figure out where the mouse was at. Then I had an idea, what if we check if the mouse is inside a button, as we draw it? It is definitely the way to go, and so we will create our own function to draw each icon that records if the mouse is inside it or not.

Ok so on to the rest of the tutorial. If you haven't yet learned these features, you will know them pretty well by the time we are done. For our drag and drop system we will be using:

  • Classes
  • Multi-Dimensional Arrays.
  • Passing variables by Reference (kinda similar to pointers in C++)

Sounds confusing and scary doesn't it? It's not, just bare with me and I will explain them to you as we go.

Step 2: Project Setup

First and foremost, create a new project in Unity, and chose not to import any of the standard packages. We will be working with a clean scene.

I have created some test icons we can use and can be downloaded here: Drag and Drop

So download the zip and extract the icons. Import them into your project using Assets > Import new asset...

I like nice, clean, crisp images when working with the GUI, so click on each image and in the Insepector set the Texture Format to ARGB32, and click the apply button. Our system will rely on manually loading resources, so create a Resources folder in your project and dump all the images in there. In case you haven't done this before, if you make a Resources folder, anything you put in there you can load using Resources.Load()

Step 3: Item Classes

We will be using classes for storing information about the items in the spellbar. There are different ways to work with classes. The method I am going to show you is my favorite because of its ease of use. We are going to be creating 2 item classes, the evil smile class and the fist punch class. (exciting, I know!)

To start, create 2 new javascript assets and name one classEvilSmile.js and the other classFistPunch.js. The beauty of these is you don't have to attach them to anything, as long as you create them you can access them anywhere.

Copy and paste this into the classEvilSmile.js: <javascript> class classEvilSmile{ // Class Properties var type = "action"; // Class type (checked if slot has restrictions in place)

// Action Properties static var icon : Texture2D = Resources.Load("action-icon-2"); static var description : String = "Evil Smiley Attack.";

// The generic use function for the action function use(){ Debug.Log(description); // Do stuff } } </javascript>

And copy and paste this into the classFistPunch.js: <javascript> class classFistPunch{ // Class Properties var type = "action"; // Class type (checked if slot has restrictions in place)

// Action Properties static var icon : Texture2D = Resources.Load("action-icon-1"); static var description : String = "Super Fist Punch Attack!";

// The generic use function for the action function use(){ Debug.Log(description); // Do stuff } } </javascript> So let's go over all the attributes. First one is the type. We set what type of item this class is. Later on, we will add restrictions to the slot bars about what items are allowed in them. They will check each item's type and if it matches then the item will be allowed to be in that slot.
Next is the icon. This is the icon the item will use when it is docked in a slot.
Then we have it's description. When we click on an item, it will call the use() function, and we have it just printing the description of the item, but you could put any code you wanted in the use() function.

Step 4: Creating the main HUD script

This script is going to be somewhat long, but I will go over each section and try to explain what is going on. Ok, so we need an empty GameObject and a script to control the HUD. Create an empty GameObject, name it something like _Setup (or whatever you want), create a new javascript and name it HUD and attach it to the GameObject. Now open the HUD script and delete everything inside and paste this in there: <javascript> // Texture variables private var icon1 : Texture2D = Resources.Load("action-icon-1"); private var icon2 : Texture2D = Resources.Load("action-icon-2"); private var highlight : Texture2D = Resources.Load("slot-highlight"); private var activated : Texture2D = Resources.Load("slot-active-highlight"); private var slotBG : Texture2D = Resources.Load("slots-bg");

private var clickInfo = new Array(); // Information where the mouse has clicked private var hoverInfo = new Array(); // Information where the mouse is currently hovering private var dragMode : boolean = false; // If we are dragging or not

private var actionList = new Array(); // List of action classes, rects, and requirements private var bagList = new Array(); // List of bag classes, rects, and requirements

function Start(){ // Set actionList attributes actionList = new Array(12); for(i=0; i<actionList.length; i++) actionList[i] = new Array(4); for(i=0; i<actionList.length; i++){ actionList[i][1] = Rect(203+(i*29),203, 24, 24); // Set slot rect actionList[i][2] = new Array("action"); // Set slot restrictions } // Set actionList keymappings actionList[0][3] = KeyCode.Alpha1; actionList[1][3] = KeyCode.Alpha2; actionList[2][3] = KeyCode.Alpha3; actionList[3][3] = KeyCode.Alpha4; actionList[4][3] = KeyCode.Alpha5; actionList[5][3] = KeyCode.Alpha6; actionList[6][3] = KeyCode.Alpha7; actionList[7][3] = KeyCode.Alpha8; actionList[8][3] = KeyCode.Alpha9; actionList[9][3] = KeyCode.Alpha0; actionList[10][3] = KeyCode.Minus; actionList[11][3] = KeyCode.Equals;

// Set bagList attributes bagList = new Array(12); for(i=0; i<bagList.length; i++) bagList[i] = new Array(3); for(i=0; i<bagList.length; i++){ bagList[i][1] = Rect(203+(i*29), 237, 24, 24); // Set slot rect bagList[i][2] = new Array("action"); // Set slot restrictions } }

function Update (){ // Handle mouse button down event if(Input.GetMouseButtonDown(0)) storeMouseLoc(); // Handle mouse down event if(Input.GetMouseButton(0)) dragCheck(); // Handle mouse up event if(Input.GetMouseButtonUp(0)) compareMouseLoc(); }

function OnGUI(){ // Reset mouse over info to null hoverInfo = new Array();

// Draw the slot backgrounds GUI.DrawTexture(Rect(200,200,349,30), slotBG); GUI.DrawTexture(Rect(200,234,349,30), slotBG);

// Add to buttons if(GUI.Button(Rect(200,300,200,25), "Add items to action bar")){ var openSlot = getNextSlot(actionList); if(openSlot != -1){ actionList[openSlot][0] = new classEvilSmile(); } } if(GUI.Button(Rect(200,330,200,25), "Add items to bags")){ openSlot = getNextSlot(bagList); if(openSlot != -1) bagList[openSlot][0] = new classFistPunch(); }

// Draw Bag GUI for(i=0; i<actionList.length; i++){ // If there is an item class in the slot, draw the button if(actionList[i][0] != null) drawButton(actionList[i][1], actionList[i][0].icon, i, actionList); // Otherwise just check if the mouse is over the rect else checkRectHover(actionList[i][1], null, i, actionList); }

// Draw Action Bar GUI for(i=0; i<bagList.length; i++){ // If there is an item class in the slot, draw the button if(bagList[i][0] != null) drawButton(bagList[i][1], bagList[i][0].icon, i, bagList); // Otherwise just check if the mouse is over the rect else checkRectHover(bagList[i][1], null, i, bagList); }

// Handle keyboard input getKeyInput();

// If we are dragging, draw the drag icon if(dragMode) drawDragIcon(); }

// Custom function to draw a GUI icon and store information at the same time function drawButton(rect, image, index, pointer){ var drawIcon : boolean = true; // Boolean for if we should draw the icon (dragging) if(clickInfo.length > 0) // If there was a previous click if(clickInfo[0] == rect && dragMode) // If the clicked rectangle is this rect and we are dragging drawIcon = false; // Dont draw the icon if(drawIcon) // If we are good to draw the icon GUI.DrawTexture(rect, image); // Draw the icon checkRectHover(rect, image, index, pointer); // Now check if the mouse is hovering over the rect }

// Checks if mouse is hovering over a rect function checkRectHover(rect, image, index, pointer){ if(rect.Contains(getMousePos())){ // Check for mouse over hoverInfo.Push(rect); // Store the rect info hoverInfo.Push(image); // Store the icon info hoverInfo.Push(index); // Store the index info hoverInfo.Push(pointer[index][0]); // Store if slot contains an obj hoverInfo.Push(pointer); // Pointer to class array hoverInfo.Push(getMousePos()); // Store click position if(pointer[index][0]){ // If there is an item that our mouse is over if(!Input.GetMouseButton(0)) GUI.DrawTexture(rect, highlight); // Draw mouse over highlight else GUI.DrawTexture(rect, activated); // Draw in use highlight } } }

// Stores current location on mouse down, only if we are above an item function storeMouseLoc(){ if(hoverInfo.length > 0 && hoverInfo[3] != null) clickInfo = hoverInfo; }

// Checks for mouse dragging function dragCheck(){ if(clickInfo.length > 0) if((getMousePos() - clickInfo[5]).sqrMagnitude > 60) dragMode = true; }

// Compares current location to stored location on mouse up function compareMouseLoc(){ // If we clicked a slot, and we are above a slot now, and there was an item in the previous slot if(hoverInfo.length > 0 && clickInfo.length > 0){ // If we are in the same bar and same location and not dragging, use the item if(hoverInfo[2] == clickInfo[2] && checkReq(clickInfo[4][hoverInfo[2]][0].type, hoverInfo[4][hoverInfo[2]][2]) && !dragMode){ // If there is an item in the slot, use it if(clickInfo[4][clickInfo[2]][0]){ clickInfo[4][clickInfo[2]][0].use(); } } // If we are in the same bar and same location and dragging, do nothing if(hoverInfo[2] == clickInfo[2] && hoverInfo[4] == clickInfo[4] && dragMode){ } // If there is an item in the slot we are moving to if(hoverInfo[4][hoverInfo[2]][0]){ var clickedItemReq = checkReq(clickInfo[4][clickInfo[2]][0].type, hoverInfo[4][hoverInfo[2]][2]); var hoverItemReq = checkReq(hoverInfo[4][hoverInfo[2]][0].type, clickInfo[4][clickInfo[2]][2]); if(clickedItemReq && hoverItemReq){ var clickSwap = clickInfo[4][clickInfo[2]][0]; var hoverSwap = hoverInfo[4][hoverInfo[2]][0]; clickInfo[4][clickInfo[2]][0] = hoverSwap; hoverInfo[4][hoverInfo[2]][0] = clickSwap; } } // If the slot is empty that we are moving to else{ // Check if click item can move to empty slot if(checkReq(clickInfo[4][clickInfo[2]][0].type, hoverInfo[4][hoverInfo[2]][2])){ hoverInfo[4][hoverInfo[2]][0] = clickInfo[4][clickInfo[2]][0]; clickInfo[4][clickInfo[2]][0] = null; } } } // If we clicked an item, but are not above an item now, destroy the item else if(hoverInfo.length == 0 && clickInfo.length > 0){ clickInfo[4][clickInfo[2]][0] = null; } clickInfo = new Array(); // Reset mouse clickInfo dragMode = false; // Reset dragMode }

// Returns a rect that is slightly smaller then the given rect function getHoverRect(hoverRect){ hoverRect.x += 1; hoverRect.y += 1; hoverRect.width -= 1; hoverRect.height -= 1; return hoverRect; }

// Returns the next available open slot in specified list, -1 if none found function getNextSlot(list){ var slotFound = -1; for(i=0; i<list.length; i++){ if(!list[i][0]){ slotFound = i; break; } } return slotFound; }

// Returns true or false if item meets list requirements function checkReq(item, list){ var result = false; for(i=0; i<list.length; i++){ if(item == list[i]){ result = true; break; } } return result; }

// Draws the icon that is dragging function drawDragIcon(){ var pos = getMousePos(); GUI.DrawTexture(Rect(pos.x - 15, pos.y - 15, 24, 24), clickInfo[1]); }

// Returns the real mouse position function getMousePos(){ var pos : Vector2 = new Vector2(Input.mousePosition.x, (Screen.height - Input.mousePosition.y)); return pos; }

// Function that checks for keyboard input from user function getKeyInput(){ for(i=0; i<actionList.length; i++){ if(Input.GetKey(actionList[i][3]) && actionList[i][0]){ GUI.DrawTexture(actionList[i][1], activated); } else if(Input.GetKeyUp(actionList[i][3]) && actionList[i][0]){ actionList[i][0].use(); } } } </javascript>

Step 5: Overview

Long script eh? Ok, if you haven't already done this, go to the top of the UniSciTE editor and select View > Toggle all Folds. This will collapse each function down to a more readable format.


Am going to have to continue this later. Explanations coming soon.

Personal tools