Unity零基础跟练制作独立游戏:拖拽事件、预制体、跨类调用方法
Unity零基础跟练制作独立游戏:拖拽事件、预制体、跨类调用方法
本篇文章是Unity游戏开发入门教程系列的第五篇,主要讲解了如何实现按钮拖拽功能、创建和使用预制体以及跨类调用方法和传递参数。通过本篇教程,读者可以掌握这些基本的Unity开发技能,为制作独立游戏打下坚实的基础。
一、按钮拖拽功能实现
由于暂时不需要双击和长按的鼠标输入事件,先在节点中删去Mouse Event 脚本。点标签右上角打开更多选项,选择Remove Component。
首先来实现节点拖拽功能,在nodes脚本中添加函数:
public void OnMouseDrag()
{
Debug.Log("正在拖拽");
}
把函数与button上的event Trigger 组件绑定:先把之前Event Trigger中留下的无效的长按事件删掉,添加拖拽事件,绑定nodes物体的nodes脚本,选其中的onMouseDrag函数。
测试观察控制台日志,发现鼠标按住按钮并拖动时,控制台会在每一帧持续输出“正在拖拽”,并且只有鼠标同时按住并拖动才会输出日志。
要实现拖动按钮的操作,需要鼠标在按钮上按住并拖动,并持续更新按钮的位置到鼠标实时所在的位置。
为了获取鼠标所在的位置,就需要先了解Unity当中坐标系的概念。鼠标和按钮所在的坐标系并不相同,按钮所在的是世界坐标系,而鼠标位置使用的是屏幕坐标系,二者并不统一。
简单用代码演示下:
void Update() //Update:在游戏中每帧都会调用一次的方法
{
//演示鼠标的屏幕坐标系和节点的世界坐标系
if (Input.GetMouseButtonDown(0))
{
Debug.Log("MousePosition: " + Input.mousePosition);
Debug.Log("NodePosition:" + nodeButton.GetComponent<Transform>().position);
}
}
下面控制台中,是我依次点击屏幕的左下角和中间节点所在处产生的输出 ,分别是
MousePosition:(5,8,0)NodePosition:(0,0,90)
和
MousePosition:(575,265,0)NodePositon:(0,0,90)
可以看到,屏幕坐标系和世界坐标系明显不同,如果直接将鼠标的位置赋值给节点的位置,一定不会得到理想的结果。所以需要先将鼠标位置信息转换为世界坐标系下的位置信息。
在刚刚创建的OnMouseDrag函数中添加下面代码,令Nodes自身的位置与转换后的鼠标位置信息同步。
//获取鼠标所在的位置,将鼠标位置信息转换为世界坐标系对应的位置信息
Vector2 pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
this.transform.position = pos; //更改物体自身的位置到鼠标对应位置
注:在C#中,this关键字表示当前对象的实例,this可以访问当前对象的属性、方法等。
这里的this代表的是unity中的Nodes这个物体。因为Nodes这个物体是最终实例化的对象。
这里脚本中不存在重名的属性,this可以省略。
保存后测试,可以看到节点成功被拖动。
二、创建和使用预制体
游戏中会存在多个功能属性相似的节点,为了方便以后统一调用,可以将这个节点保存为预制体。
在Assets文件夹内创建新的文件夹,命名为Prefabs,用来保存预制体。打开Prefabs,把层级目录中的Nodes拖入Prefabs中,这样就创建了一个节点的预制体。节点变成预制体后,也就可以作为模板,使用代码随意调用,省去了很多重复工作。
创建预制体后,删掉场景中的Nodes,在Script中创建一个新的脚本,命名为Game。对脚本进行代码的编写:
public class Game : MonoBehaviour
{
//声明一个变量,用来指明要创建的是哪个预制体
public Nodes node;
// Start is called before the first frame update
void Start()
{
this.CreateNode();
}
//创建一个方法,每次调用该方法都可以实现创造物体
public void CreateNode()
{
Debug.Log("开始创建物体");
}
// Update is called once per frame
void Update()
{
}
}
首先在类当中声明一个变量,用来指明要创建的是哪个预制体,这里Nodes是脚本Nodes中的类。
之后创建一个方法CreateNode,之后每次调用方法都会根据预制体创建物体。现在其中填写上debug语句方便理解。
回到编辑器,将Game这个脚本附加到Canvas,使脚本能够生效。
之后把预制体nodes绑定在Node属性上。以后我们对node这个变量的改动都会反映到预制体创建的物体上。
注意要将Nodes这个预制体拖选到菜单,然后编辑器会自动从物体上获取对应的脚本。
这里括号外代表的是预制体,括号内代表的是Nodes脚本。
经过测试,脚本中的方法成功调用。
给CreateNode方法中添加下面的代码:
Instantiate(node, this.transform); //创建一个物体,并让他从属于canvas的子物体
这里创建了一个将canvas指定为父物体的节点复制体。其位置和旋转取默认值。
Instantiate是对预制体进行实例化的方法,默认格式为Instantiate(prefab, position, rotation, parentTransform)。
其中prefab代表作为模板的预制体,position表示创建的新对象的位置坐标,rotation表示新对象相对世界坐标的旋转角度。在最后可以添加上希望成为新实例父物体的unity对象的transform组件来指定父物体。
prefab是必要的,position、rotation、parentTransform都可省略。位置和旋转省略则取预制体当中的默认值,父物体省略则不会指定父物体。
回到编辑器测试,界面中心位置创建了初始节点,且其他功能与预制体相同。
物体列表中会多出用脚本创建的子物体nodes(Clone),停止运行后消除。
三、跨类调用方法和传递参数
在nodes脚本中添加新的函数initData:
public void initData(string titleText) //初始化节点数据
{
//在创建节点的同时,修改它的文本内容
nodeTitle.text = titleText; //将标题文本设置为调用该方法时传递过来的字符串
}
这个函数用来初始化节点的数据,在调用该函数时,会将节点标题改为调用函数输入的文本。
修改Game脚本中的CreateNode函数:
public void CreateNode()
{
//Debug.Log("开始创建物体");
Nodes n = Instantiate(node, this.transform); //创建一个物体,并让他从属于canvas的子物体;这里的this指脚本附加的canvas
//instantiate是对预制体进行实例化的方法,在最后添加上希望成为新实例父物体的unity对象的transform组件来指定父物体
n.initData("NewTitle");
}
这里由于需要对新对象的内容进行修改,先用临时变量保存下来。之后n.initData() 成功调用n对应的Notes类下的 public 公共方法 initData ,由于在方法中要求了接收string参数,用()调用后必须加入传递的string数据。
这样就成功在Nodes类新建了一个public方法,并在Game类的CreateNode方法中跨类调用了该方法,并通过参数传递成功为节点的标题赋值。
可以看到运行测试后标题改变为初始化要求的值。
本篇继续实现了拖拽这一鼠标事件,不过设计还是过于简单,鼠标若在节点偏离中心点的位置拖拽,节点中心一开始就会瞬移到鼠标位置。 学习了创建预制体和通过预制体生成对象的方法。学习了跨类调用自定义方法,并传递参数的操作。