Creating a Drag and Drop Spell Bar

From Unify Community Wiki
(Difference between revisions)
Jump to: navigation, search
(Step 5: Custom Functions Continued)
m (Text replace - "</javascript>" to "</syntaxhighlight>")
 
(24 intermediate revisions by 4 users not shown)
Line 4: Line 4:
 
==Description:==
 
==Description:==
 
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.
 
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.
 +
 +
==''Note:''==
 +
 +
''I discovered this incomplete tutorial and found value to it. It's worth a test and a read-thru.''
 +
 +
''However, I have since found that many of the methods that are described in this tutorial can be found in the existing Unity "events" methods. These "events" are poorly documented, unfortunately. One should read this tutorial, and then look thru the docs for additional information on Events (http://unity3d.com/support/documentation/ScriptReference/Event.html) and EventTypes (http://unity3d.com/support/documentation/ScriptReference/EventType.html). These can include '''"if (myCurrentRect.Contains(Event.current.mousePosition)) {}"''' and '''"if (Event.current.type == EventType.MouseDrag) {}"''' and '''"if (Event.current.delta.magnitude > myDeltaThreshold) {}"'''. Often it is recommended that one save the current event as: "var currentEvent : Event = Event.current;" and then use currentEvent for the rest of the method to avoid asking for Event.current repeatedly."'' - Little Angel
  
 
==Step 1: Plan what you want to do before you do it==
 
==Step 1: Plan what you want to do before you do it==
Line 33: Line 39:
 
<li>Passing variables by Reference (kinda similar to pointers in C++)
 
<li>Passing variables by Reference (kinda similar to pointers in C++)
 
</ul>
 
</ul>
Sounds confusing and scary doesn't it? It's not, just bare with me and I will explain them to you as we go.
+
Sounds confusing and scary doesn't it? It's not, just bear with me and I will explain them to you as we go.
  
==Step 3: Creating the backbone==
+
==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.<br><br>
 
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.<br><br>
 
I have created some test icons we can use and can be downloaded here:
 
I have created some test icons we can use and can be downloaded here:
Line 44: Line 50:
 
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()<br><br>
 
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()<br><br>
  
Ok, now 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:
+
==Step 3: Item Classes==
<javascript>
+
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!)
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
+
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.
private var hoverInfo = new Array(); // Information where the mouse is currently hovering
+
private var dragMode : boolean = false; // If we are dragging or not
+
  
function Start(){
+
Copy and paste this into the '''classEvilSmile.js''':
}
+
<syntaxhighlight lang="javascript">
function Update (){
+
class classEvilSmile{
}
+
// Class Properties
function OnGUI(){
+
var type = "action"; // Class type (checked if slot has restrictions in place)
}
+
</javascript>
+
  
At the top we declare a '''highlight''', '''activated''', and '''slotBG''' variables and tell Unity to load the corresponding textures at startup. We also declared a '''clickInfo''' array, a '''hoverInfo''' array, and a '''dragMode''' boolean.<br><br>
+
// Action Properties
The drag and drop system will basically work like this:<br>
+
static var icon : Texture2D = Resources.Load("action-icon-2");
The '''hoverInfo''' array will constantly store information about what icon the mouse is currently hovering over, or it will be empty if we aren't over anything. If the user clicks the mouse button down, and they are over an icon, '''hoverInfo''' will copy everything it has to the '''clickInfo''' array. When the user releases the mouse button, we do all the checks to determine what happened, and if we drag and dropped something. A simple example would be, if hoverInfo is not the same as '''clickInfo''', then we dragged the icon from one slot to another. If '''hoverInfo''' and '''clickInfo''' are the same, then we just pressed the button, etc.
+
static var description : String = "Evil Smiley Attack.";
  
==Step 4: Continuing on==
+
// The generic use function for the action
Ok so under the OnGUI() function, put this code:
+
function use(){
<javascript>
+
Debug.Log(description); // Do stuff
function OnGUI(){
+
}
// Reset mouse over info to null
+
hoverInfo = new Array();
+
 
}
 
}
</javascript>
+
</syntaxhighlight>
This is very improtant, because the '''hoverInfo''' array needs to be reset each '''OnGUI()''' cycle. So what we are doing is at the start of each '''OnGUI()''' cycle, we reset the array, then we draw all the icons. If we are inside one of the icons, the '''hoverInfo''' array will be filled out with all the information about that icon. If we continue to keep the mouse over that icon, it will get reset at the begining of '''OnGUI()''' and be set back to whatever icon we are hovering over. Confusing yet? No? Good! :)
+
  
Now lets add the slot backgrounds and buttons. Add the following code to your OnGUI() function:
+
And copy and paste this into the '''classFistPunch.js''':
<javascript>
+
<syntaxhighlight lang="javascript">
function OnGUI(){
+
class classFistPunch{
// Reset mouse over info to null
+
// Class Properties
hoverInfo = new Array();
+
var type = "action"; // Class type (checked if slot has restrictions in place)
+
// Draw the slot backgrounds
+
GUI.DrawTexture(Rect(200,200,349,30), slotBG);
+
GUI.DrawTexture(Rect(200,234,349,30), slotBG);
+
  
if(GUI.Button(Rect(200,300,200,25), "Add items to action bar")){
+
// Action Properties
}
+
static var icon : Texture2D = Resources.Load("action-icon-1");
if(GUI.Button(Rect(200,330,200,25), "Add items to bag bar")){
+
static var description : String = "Super Fist Punch Attack!";
 +
 
 +
// The generic use function for the action
 +
function use(){
 +
Debug.Log(description); // Do stuff
 
}
 
}
 
}
 
}
</javascript>
+
</syntaxhighlight>
We will leave the buttons blank for now and fill them in later. Lets create the structures that will actually hold our icons! For this we will be using multi-dimensional arrays. If you haven't used those before, basically its a fancy word for an array of arrays. So at the top of our script lets declare 2 new variables, '''actionList''' and '''bagList'''. These correspond to the two slot bars that we have already made. So our script should look like this so far:
+
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.<br>
<javascript>
+
Next is the icon. This is the icon the item will use when it is docked in a slot.<br>
private var highlight : Texture2D = Resources.Load("slot-highlight");
+
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.
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
+
==Step 4: Creating the main HUD script==
private var hoverInfo = new Array(); // Information where the mouse is currently hovering
+
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:
private var dragMode : boolean = false; // If we are dragging or not
+
<syntaxhighlight lang="javascript">// Texture variables
 +
private var icon1 : Texture2D;
 +
private var icon2 : Texture2D;
 +
private var highlight : Texture2D;
 +
private var activated : Texture2D;
 +
private var slotBG : Texture2D;
  
private var actionList = new Array(); // List of action classes, rects, and requirements
+
private var clickInfo = new Array(); // Information where the mouse has clicked
private var bagList = new Array(); // List of bag classes, rects, and requirements
+
private var hoverInfo = new Array(); // Information where the mouse is currently hovering
 +
private var dragMode : boolean = false; // If we are dragging or not
  
function Start(){
+
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 Awake  () {
 +
// These should be loaded in Awake  ().
 +
// Resouces.Load doesn't like being called in the declaration stage.
 +
icon1 = Resources.Load("action-icon-1");
 +
icon2 = Resources.Load("action-icon-2");
 +
highlight = Resources.Load("slot-highlight");
 +
activated = Resources.Load("slot-active-highlight");
 +
slotBG = Resources.Load("slots-bg");
 
}
 
}
function Update (){
 
}
 
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);
 
  
if(GUI.Button(Rect(200,300,200,25), "Add items to action bar")){
+
function Start() {
}
+
if(GUI.Button(Rect(200,330,200,25), "Add items to bag bar")){
+
}
+
}
+
</javascript>
+
Now we created the '''actionList''' and '''bagList''' arrays, but we need them to be multi-dimensional, right now they would just be a single list, so under the '''Start()''' function, add the following code:
+
<javascript>
+
function Start(){
+
 
// Set actionList attributes
 
// Set actionList attributes
 
actionList = new Array(12);
 
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] = new Array(4);
for(i=0; i<actionList.length; i++){
+
for(i=0; i<actionList.length; i++) {
actionList[i][1] = Rect(203+(i*29),203, 24, 24); // Set slot rect
+
actionList[i][1] = Rect(203+(i*29),203, 24, 24);       // Set slot rect
actionList[i][2] = new Array("action"); // Set slot restrictions
+
actionList[i][2] = new Array("action");     // Set slot restrictions
 
}
 
}
 
// Set actionList keymappings
 
// Set actionList keymappings
Line 149: Line 142:
 
// Set bagList attributes
 
// Set bagList attributes
 
bagList = new Array(12);
 
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] = 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++){
 
for(i=0; i<bagList.length; i++){
bagList[i][1] = Rect(203+(i*29), 237, 24, 24); // Set slot rect
+
// If there is an item class in the slot, draw the button
bagList[i][2] = new Array("item", "bag", "action"); // Set slot restrictions
+
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();
 
}
 
}
</javascript>
 
Kinda confusing at first, so lets take a look at the '''bagList''' attributes. The '''actionList''' section is the same, but with the KeyCode section added. First we set '''bagList''' to a new Array(12), meaning we want a list that contains 12 entries (we have 12 slots per slot bar). Then we have a For Loop that makes a new Array(3) in each one of the bagList entries. So a list thats 3 entries long inside each entry in the main list. Then we have another For Loop that sets values in the 2nd and 3rd entry but leaves the 1st one blank. Our lists will have the following structure:
 
<javascript>
 
bagList[0]  -  [0]item class [1]slot Rect [2]slot restrictions
 
bagList[1]  -  [0]item class [1]slot Rect [2]slot restrictions
 
bagList[2]  -  [0]item class [1]slot Rect [2]slot restrictions
 
bagList[3]  -  [0]item class [1]slot Rect [2]slot restrictions
 
bagList[4]  -  [0]item class [1]slot Rect [2]slot restrictions
 
bagList[5]  -  [0]item class [1]slot Rect [2]slot restrictions
 
bagList[6]  -  [0]item class [1]slot Rect [2]slot restrictions
 
bagList[7]  -  [0]item class [1]slot Rect [2]slot restrictions
 
bagList[8]  -  [0]item class [1]slot Rect [2]slot restrictions
 
bagList[9]  -  [0]item class [1]slot Rect [2]slot restrictions
 
bagList[10] -  [0]item class [1]slot Rect [2]slot restrictions
 
bagList[11] -  [0]item class [1]slot Rect [2]slot restrictions
 
</javascript>
 
So for each slot in '''bagList[i]''', inside it we have access to what icon class is stored in the slot '''bagList[i][0]''', the slot Rect '''bagList[i][1]''' and any slot restrictions that the slot might have '''bagList[i][2]'''. You can drill down to each sub list by putting more [] after the index that you want (but only up to how many levels you make). The '''actionList''' is exactly the same, but we built an array that has 4 indexes per slot instead of 3 like the '''bagList''', and we store which KeyCode corresponds to that slot. We could have done this with multiple Arrays, but I find it easier / cleaner to just work with one.
 
  
==Step 5: Custom Functions!==
+
// Custom function to draw a GUI icon and store information at the same time
First, lets add our custom function for drawing the icons. After all the code we have, put this function in your script:
+
<javascript>
+
 
function drawButton(rect, image, index, pointer){
 
function drawButton(rect, image, index, pointer){
 
var drawIcon : boolean = true; // Boolean for if we should draw the icon (dragging)
 
var drawIcon : boolean = true; // Boolean for if we should draw the icon (dragging)
Line 185: Line 219:
 
checkRectHover(rect, image, index, pointer); // Now check if the mouse is hovering over the rect
 
checkRectHover(rect, image, index, pointer); // Now check if the mouse is hovering over the rect
 
}
 
}
</javascript>
+
 
It has a lot of comments, so hopefully those help you to understand whats going on, but I will elaborate. So when we want to draw an icon / button, in our list we have the Rect value, the item class (which will have an image for it's icon), we know it's index in the list array and what the list is called. So we pass all these values to our '''drawButton()''' function. First inside the function we declare a variable called '''drawIcon''' and set it to true. When you click an icon and drag it, this will tell Unity to stop drawing the original icon and only draw the one that is dragging. Otherwise we would have 2 icons, one in the original slot and the one that follows the mouse.<br>
+
// Checks if mouse is hovering over a rect
Then we do checks to determine if we should draw the icon in the slot. If '''clickInfo'''.length is greater then 0 ('''clickInfo''' will only have a value if we were hovering over something and clicked it, then '''hoverInfo''' passes the values it has to '''clickInfo'''), then if '''clickInfo''''s rect is the same as the rect that we passed from '''hoverInfo''', we set '''drawIcon''' to false.<br>
+
If '''drawIcon''' passed the checks then we hand off all the values to a new function called '''checkRectHover()'''. Lets create that one now:<br>
+
<javascript>
+
 
function checkRectHover(rect, image, index, pointer){
 
function checkRectHover(rect, image, index, pointer){
 
if(rect.Contains(getMousePos())){ // Check for mouse over
 
if(rect.Contains(getMousePos())){ // Check for mouse over
Line 206: Line 237:
 
}
 
}
 
}
 
}
</javascript>
+
 
So all the values get passed to this function. Then all the values get promoted to '''hoverInfo'''. The reason these 2 functions are broken up is because when we draw the icon (you'll see in a bit) we check if there is an item in our list for that slot, if there is we pass it off to '''drawButton()''' otherwise we send the info to '''checkRectHover()'''. We have to know if we are above an icon, but we also have to know if we are above an empty slot. Splitting these into two separate functions allows us to check for both. At the top of '''checkRectHover()''' we call another function called '''getMousePos()'''. This function is super simple and looks like this:
+
// Stores current location on mouse down, only if we are above an item
<javascript>
+
function storeMouseLoc(){
function getMousePos(){
+
if(hoverInfo.length > 0 && hoverInfo[3] != null)
var pos : Vector2 = new Vector2(Input.mousePosition.x, (Screen.height - Input.mousePosition.y));
+
clickInfo = hoverInfo;
return pos;
+
 
}
 
}
</javascript>
 
Input.mousePosition returns a value where the Y values is inverted (compared to how Rect's are created), so this function simply returns the correct value of the mouse position.
 
  
==Step 6: Custom Functions Continued==
 
Lets add 4 more simple functions that will help us out:
 
<javascript>
 
 
// Checks for mouse dragging
 
// Checks for mouse dragging
 
function dragCheck(){
 
function dragCheck(){
Line 224: Line 249:
 
if((getMousePos() - clickInfo[5]).sqrMagnitude > 60) dragMode = true;
 
if((getMousePos() - clickInfo[5]).sqrMagnitude > 60) dragMode = true;
 
}
 
}
</javascript>
 
'''dragCheck()''' will check if we are dragging and icon and set '''dragMode''' to true. If you look at '''checkRectHover()''', in the 5th index, we store where the mouse is, and when we click the mouse down, the location the mouse is in gets stored in '''clickInfo'''. We then compare the current mouse position by calling the function again to get the updated mouse position. We then subtract the values of the old position vector and new current position vector, then do a sqrMagnitude which gives us a float value of how far the mouse moved and campare it to a value. I have it set to 60. Basically this means you have to click and drag the icon a bit before it disengages. The higher the value (60) the more you have to drag it to dislodge it from the slot.
 
  
<javascript>
 
// 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]);
 
}
 
function storeMouseLoc(){
 
if(hoverInfo.length > 0 && hoverInfo[3] != null)
 
clickInfo = hoverInfo;
 
}
 
</javascript>
 
These 4 functions are pretty self explanatory. '''checkReq()''' will check if the icon can be stored in the slot you are dragging it to. Each item class will have a .type attribute, and each slot that we have created so far stores what types of items can be stored in them. '''actionList'''[i][2] is set to "action". '''bagList'''[i][2] is set to "item", "bag", and "actions". '''checkReq()''' will return true, only if the icon is able to be dropped into that slot.<br>
 
<br>
 
Now its time for the most beefy (possibly most confusing) function that we have to make:
 
<javascript>
 
 
// Compares current location to stored location on mouse up
 
// Compares current location to stored location on mouse up
 
function compareMouseLoc(){
 
function compareMouseLoc(){
Line 278: Line 262:
 
}
 
}
 
// If we are in the same bar and same location and dragging, do nothing
 
// 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(hoverInfo[2] == clickInfo[2] && hoverInfo[4] == clickInfo[4] && dragMode){
 +
}
 
// If there is an item in the slot we are moving to
 
// If there is an item in the slot we are moving to
 
if(hoverInfo[4][hoverInfo[2]][0]){
 
if(hoverInfo[4][hoverInfo[2]][0]){
Line 304: Line 289:
 
}
 
}
 
clickInfo = new Array(); // Reset mouse clickInfo
 
clickInfo = new Array(); // Reset mouse clickInfo
dragMode = false; // Reset dragMode
+
dragMode = false; // Reset dragMode
 
}
 
}
</javascript>
+
 
This is our logic function, and it gets called on MouseUp. Basically this compares the information it finds in '''hoverInfo''' and '''clickInfo'''. It then decides what should happen based on the values. The most important part about this function is how we store information in the '''clickInfo''' and '''hoverInfo''' arrays. To recap, the structure is as follows:<br>
+
// Returns a rect that is slightly smaller then the given rect
<javascript>
+
function getHoverRect(hoverRect){
hoverInfo[0] = Rect of slot
+
hoverRect.x += 1;
hoverInfo[1] = Image of item class
+
hoverRect.y += 1;
hoverInfo[2] = Index of item in the list
+
hoverRect.width -= 1;
hoverInfo[3] = This basically stores if there is an item in this index of the list, or if its blank
+
hoverRect.height -= 1;
hoverInfo[4] = The name of the list we are currently working on
+
return hoverRect;
hoverInfo[5] = Mouse position
+
}
</javascript>
+
 
And you can access items in a list like so: '''list'''[0][2], '''list'''[1][0], etc. So when you see something like this:
+
// Returns the next available open slot in specified list, -1 if none found
<javascript>
+
function getNextSlot(list){
clickInfo[4][clickInfo[2]][0].type
+
var slotFound = -1;
</javascript>
+
for(i=0; i<list.length; i++){
Break it down to each component of the '''hoverInfo''' or '''clickInfo''' arrays. '''clickInfo'''[4] = the array we are working on. clickInfo[2] = the index of the list we are working on, and anything stored in the [0] slot is the item class stored in the slot. When accessing class attributes, you put a period, then the name of the attribute. So that code would really mean something like this:
+
if(!list[i][0]){
<javascript>
+
slotFound = i;
clickInfo[4][clickInfo[2]][0].type
+
break;
to
+
}
bagList[4][0].type
+
}
</javascript>
+
return slotFound;
We have one more function to make, and thats this one:
+
}
<javascript>
+
 
 +
// 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(){
 
function getKeyInput(){
 
for(i=0; i<actionList.length; i++){
 
for(i=0; i<actionList.length; i++){
Line 338: Line 348:
 
}
 
}
 
}
 
}
</javascript>
+
</syntaxhighlight>
It goes through the '''actionList''' array, gets the keycodes stored in the index [3], and tests if the user is pressing the key. If they are, it activates the item in the slot's '''use()''' function.<br><br>
+
 
One last thing we have to do is edit our '''Update'''() function to actually use our custom functions. Edit your '''Update'''() function so it looks like this:
+
==Step 5: Overview==
<javascript>
+
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.
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();
+
}
+
</javascript>
+
  
 
==TO BE CONTINUED==
 
==TO BE CONTINUED==
Still working on this, the formatting, etc. Will be revised!
+
Am going to have to continue this later. Explanations coming soon.
 +
 
 +
[[Category:Tutorials]]

Latest revision as of 20:52, 10 January 2012

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


Contents

[edit] Description:

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.

[edit] Note:

I discovered this incomplete tutorial and found value to it. It's worth a test and a read-thru.

However, I have since found that many of the methods that are described in this tutorial can be found in the existing Unity "events" methods. These "events" are poorly documented, unfortunately. One should read this tutorial, and then look thru the docs for additional information on Events (http://unity3d.com/support/documentation/ScriptReference/Event.html) and EventTypes (http://unity3d.com/support/documentation/ScriptReference/EventType.html). These can include "if (myCurrentRect.Contains(Event.current.mousePosition)) {}" and "if (Event.current.type == EventType.MouseDrag) {}" and "if (Event.current.delta.magnitude > myDeltaThreshold) {}". Often it is recommended that one save the current event as: "var currentEvent : Event = Event.current;" and then use currentEvent for the rest of the method to avoid asking for Event.current repeatedly." - Little Angel

[edit] 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 bear with me and I will explain them to you as we go.

[edit] 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 Icons.zip

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()

[edit] 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:

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
	}
}

And copy and paste this into the classFistPunch.js:

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
	}
}

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.

[edit] 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:

// Texture variables
private var icon1 : Texture2D;
private var icon2 : Texture2D;
private var highlight : Texture2D;
private var activated : Texture2D;
private var slotBG : Texture2D;
 
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 Awake  () {
	//	These should be loaded in Awake  ().
	//	Resouces.Load doesn't like being called in the declaration stage.
	icon1 = Resources.Load("action-icon-1");
	icon2 = Resources.Load("action-icon-2");
	highlight = Resources.Load("slot-highlight");
	activated = Resources.Load("slot-active-highlight");
	slotBG = Resources.Load("slots-bg");
}
 
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();
		}
	}
}

[edit] 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.

[edit] TO BE CONTINUED

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

Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox