2017-10-23 18:21:39 qq_33632356 阅读数 2141

我们在学c#基础的时候,大家都知道想要获取一个实例类里面的公有成员,就必须在调用的时候实例化这个类。

比如有一个A类

class  A

{

  public int  age;

}


class Progress

{

    void Main()

     {

A a = new A();//实例化A类  也称实例化对象

                a.name;

Console.WriteLine(name);


     } 

}


那么如果在Unity中你想实例化一个继承了MonoBehaviour的一个B类呢? 这样还可以吗?

比如有一个B类:

class  B  : MonoBehaviour

{

  public int  age;

}


class C : MonoBehaviour

{

     B b; 声明

    void Start()

     {

                b = new A();//实例化B类  也称实例化对象

                 b.name;

print(b.name);

     } 

}

如果像上面那样写的话,打印出来的b.name是空的,null。愿意很简单。

因为在Unity中除了对象之外都是组件,就是说继承了MonoBehaviour的类想要运行,那么必须要挂在游戏物体上(GameObject)。也就是说此时 B类 已经是一个组件了。

所以我们就要以GetConponent<B>(); 的形式去访问了。不能new。

当然你做一个单例 public static C _instance;//单例访问也可以,GameObject.FindObjectOfType<GameObject>();查找类型也可以。

方式有很多,我们这里说的是为什么不能new,大家明白这个意思就好了,个人理解,有错误的,遗漏的地方还请多多留言。







2016-05-26 21:02:34 windeer 阅读数 2297

Unity3D推崇组件式开发,而MonoBehaviour是核心所在,虽然在实际项目中,我要求尽可能使用继承自MonoBehaviour的对象(我们自己做了封装),但是对于MonoBehaviour中各种系统回调函数的执行顺序还是要了解清楚。

MonoBehaviour执行顺序图

以下是一些常用的函数调用顺序的说明:

Awake:当一个脚本实例被载入时Awake被调用,要先于Start。建议少用,此刻物体可能还没有实例化出来,会影响程序执行顺序。需要注意的是,假设实例化一个物件:

GameObject go = new GameObject();
go.GetComponent().Test();
这个Test()的调用顺序会在Awake()之后,在Start()之前

Start:Start仅在Update函数第一次被调用前调用。物体实例化完成后调用(估计,没确认)。

Update:当MonoBehaviour启用时,其Update在每一帧被调用

FixedUpdate:这个函数会在每个固定的物理时间片被调用一次.这是放置游戏基本物理行为代码的地方。UPDATE之后调用。

Reset:Reset是在用户点击检视面板的Reset按钮或者首次添加该组件时被调用.此函数只在编辑模式下被调用.Reset最常用于在检视面板中给定一个最常用的默认值.

OnDestory:物体被删除时调用。

OnEnable:物体启用时被调用。

OnDisable:物体被禁用时调用。

OnGui:这个函数会每帧调用好几次(每个事件一次),GUI显示函数只能在OnGui中调用。备注:这个各大翻译都直接翻译成每帧调用一次了。下面是官方的原文。
OnGui:This means that your OnGUI implementation might be called several times per frame (one call per event). For more information on GUI events see the Event reference. If the MonoBehaviour’s enabled property is set to false, OnGUI() will not be called.

Render中调用,是在渲染过程中回调
Unity渲染系统会根据渲染顺序调用对象的Render,然后在对应时间点,回调对象的Render方法回调

更详细的请看这里:
http://docs.unity3d.com/Documentation/ScriptReference/MonoBehaviour.html

2018-04-24 08:54:36 cdkd123 阅读数 118

tag: 常见问题

预制件的实例化,有两种方法:
1. 直接拖动到 “Hierarchy”视图中。
2. 代码生成。

出于项目需要,我需要代码中主动实例化一个预制件。犯了一个很简单的错误,记录一下。

出错描述:

我的控件叫“UIMessageCanvas”,里面有个UI.Button控件而已。

代码定义的实例化的类,如下:

class SendMessageManager : MonoBehaviour {

    private static readonly object _locker = new object();

    #region Singleton

    private static SendMessageManager _instance = null;

    public static SendMessageManager Inst
    {
        get
        {
            if (_instance == null) { 
                lock (_locker)
                {
                    if (_instance == null) {
                        GameObject obj = new GameObject(typeof(SendMessageManager).Name);
                        _instance = obj.AddComponent<SendMessageManager>();
                        //_instance = new GameObject(typeof(SendMessageManager).Name, typeof(SendMessageManager)).GetComponent<SendMessageManager>();
                        DontDestroyOnLoad(_instance);
                    }
                }
            }
            return _instance;
        }
    }

    #endregion

    void Start() {

        if (_initFinish == false) {


            _messageGameObject = Instantiate(Resources.Load<GameObject>("Prefabs/UI/UIMessageCanvas"));

            if (_messageGameObject != null) {
                _trans = _messageGameObject.transform;
            }

            Debug.Log("Call End:" + _messageGameObject == null);
            _initFinish = true;
        }
    }

    public void Click() {
        Debug.debug("按钮被点击了");
    }

    public void Show() {
    Start(); 
    }
}

我又要给按钮“Button”设置一个点击事件,所以,就在里面写了个方法”Click”, 于是,我把 SendMessageManager设置成 UIMessageCanvas 的组件,运行时,
这样使用这个类:

SendMessageManager.Inst.Show(); 实例化”UIMessageCanvas”。

测试中,发现”UIMessageCanvas”不停的在视图中生成。

图1 不停生成的UIMessageCanvas

单例方法明明写了,构造方法应该只调用了一次呀?

于是就开始想是什么原因?

后来经过公司高手帮助,原来是把 SendMessageManager设置成 UIMessageCanvas 的组件, 好给Button设置点击事件 Click()。这会主动调用SendMessageManager的构造方法,我在代码里,有主动生成这个控件,
Start 方法里,又主动实例化此控件,这就造成循环实例化 UIMessageCanvas 组件。

解决方法,就是去掉 UIMessageCanvas 的 SendMessageManager 组件,在代码中,给Button按钮,设置匿名点击事件。

buttonText.onClick.AddListener(delegate() {
            DebugLog("click button!!");
            sendMessage();
});

那么总结一下,GUI实例化有2种方式:
1. 拖动控件实例化。
2. 代码中调用:“ Instantiate()”方法实例化。

不论如何实例化,“他绑定的脚本控件,都会主动调用构造方法实例化。”

预制件里获取Button:

在代码中如何获取一个GUI控件的子控件呢?

之前,一直调用 Transform.GetComponent<Button>() 方法,但发现没用,在请教同事后发现,可以调用Transform..GetComponentInChildren<Button>();,就可以获取了。

2015-08-14 10:23:25 u010024824 阅读数 3072

Null Reference Exception : Object reference not set to an instance of an object.

异常:空引用,对象的引用未设置到对象的实例。

出现该异常的位置:

可能是在调用组件时该组件还未实例化或未存储地址指针,需要查看是否将组件位置拽入代码公布的字段;还有注意Unity3D的生命周期,调整代码的执行顺序。




(若有错误和不足之处,还望指正,谢谢!)

2017-08-10 12:44:13 u011360242 阅读数 3809

Unity3D常用API总结

一. MonoBehaviour类及查询API

MonoBehaviour是每个脚本的基类.

MonoBehaviour常用方法及调用顺序

   //最开始调用,做一些初始化工作。建议少用,此刻物体可能还没有实例化出来,会影响程序执行顺序。
    void Awake(){}
    // 不是很紧急的初始化,一般放在Start里面来做。仅在Update函数第一次被调用前调用。
    void Start(){}
    //用户点击检视面板的Reset按钮或者首次添加该组件时被调用。此函数只在编辑模式下被调用。Reset最常用于在检视面板中给定一个最常用的默认值。
    void Reset(){}
    // 每一帧调用一次,帧间隔时间有可能改变。
    void Update(){}
    //以相同时间间隔调用,用在力学更新效果中。执行在Update之前。
    void FixedUpdate(){}
    //在Update和FixedUpdate调用之后调用。一般人物的移动放在Update中,而摄像机的跟进变化放到FixedUpdate中。确保两个独立,一前一后,不产生错误。
    void LateUpdate(){}

    //On开头的方法,是由其他事件触发调用的。
    //物体被删除时调用
    void OnDestroy(){}
    //物体启用时被调用
    void OnEnable(){}
    //物体被禁用时调用
    void OnDisable(){}
    //这个函数会每帧调用好几次(每个事件一次),GUI显示函数只能在OnGUI中调用
    void OnGUI(){}

下图是单个脚本内部方法的调用顺序:
这里写图片描述

查询API:游戏蛮牛Unity3D-API

二. Input类及脚本字段属性在检视面板中的运用

输入系统的接口.
getaxis、getkey、getbutton、getjoystick等函数。

为了提高输入方式在代码中的效率,推荐用GetButton()方法,而不是GetKey().GetButton根据不同的设备进行对应的按键输入,比如手柄,PC,手机 etc.

Unity中定制按钮的方式
Edit–>project setting–>Input
具体按键设定规则见:Unity圣典Input输入

GetKey()、GetKeyDown()、GetKeyUp()方法:

    // 每一帧调用一次,帧间隔时间有可能改变。
    void Update()
    {
        //KeyCode为枚举类
        //GetKey方法:只要按下就会执行 直到不按 执行不止一次
        Input.GetKey(KeyCode.A);
        //GetKeyDown:按下按键执行 执行一次
        Input.GetKeyDown(KeyCode.A);
        //GetKeyDown:按下后松开按键执行 执行一次
        Input.GetKeyUp(KeyCode.A);
        //以上方法返回布尔值
    }

GetButton()、GetButtonDown()、GetButtonUp()方法:

    // 每一帧调用一次,帧间隔时间有可能改变。
    void Update()
    {
        //GetButton方法括号中的值为buttonName(按钮别称) 可在相关文档中查阅
        //GetButton方法:只要按下就会执行 直到不按 执行不止一次
        Input.GetButton("Jump");
        //GetButtonDown:按下按键执行 执行一次
        Input.GetButtonDown("Jump");
        //GetButtonDown:按下后松开按键执行 执行一次
        Input.GetButtonUp("Jump");
        //以上方法返回布尔值
    }

GetAxis() 获取轴

根据坐标轴名称返回虚拟坐标系中的值。
使用控制器和键盘输入时此值范围在-1到1之间。如果坐标轴设置为鼠标运动增量,鼠标增量乘以坐标轴灵敏度的范围将不是-1到1 。

C#脚本:

// A very simplistic car driving on the x-z plane.
// 一个十分简单的在x-z平面的驾车例子
using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    public float speed = 10.0F;//移动速度
    public float rotationSpeed = 100.0F;//旋转速度

    void Update() {
        // Get the horizontal and vertical axis.
        //获取横向和纵向坐标轴
        // By default they are mapped to the arrow keys.
        //默认情况下他们关联到方向键上
        // The value is in the range -1 to 1
        //值的范围是在-1到1之间
        float translation = Input.GetAxis("Vertical") * speed;
        float rotation = Input.GetAxis("Horizontal") * rotationSpeed;

        // Make it move 10 meters per second instead of 10 meters per frame...
        // 使它每帧移动10米变为每秒移动10米...
        translation *= Time.deltaTime;
        rotation *= Time.deltaTime;

        // Move translation along the object's z-axis
        //沿着z轴平移对象
        transform.Translate(0, 0, translation);

        // Rotate around our y-axis
        //以我们的y轴为中心旋转
        transform.Rotate(0, rotation, 0);
    }
}
// Performs a mouse look.
//执行一个鼠标观察
using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    public float horizontalSpeed = 2.0F;
    public float verticalSpeed = 2.0F;
    void Update() {
        // Get the mouse delta. This is not in the range -1...1
        //获取鼠标增量,范围不在-1...1
        float h = horizontalSpeed * Input.GetAxis("Mouse X");
        float v = verticalSpeed * Input.GetAxis("Mouse Y");
        transform.Rotate(v, h, 0);
    }
}

GetJoystickNames() 获取控制杆名称列表

返回一个用来描述已连接的控制杆的字符串集合。
它可以用在用户输入设置界面 –这样,你就可以把显示的标签”Joystick 1”换成意义更明确的名字像”Logitech WingMan”,读取不同控制器的值,你需要分别为各个控制器的数字指定指方向轴如果你想将其使用在输入设置中。

C#脚本:

// Prints a joystick name if movement is detected.
//如果检测到移动就输出一个控制杆名称
using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    void Update() {
        // requires you to set up axes "Joy0X" - "Joy3X" and "Joy0Y" - "Joy3Y" in the Input Manger
        //你需要在输入管理器中设置方向轴"Joy0X" - "Joy3X""Joy0Y" - "Joy3Y"
        int i = 0;
        while (i < 4) {
            if (Mathf.Abs(Input.GetAxis("Joy" + i + "X")) > 0.2F || Mathf.Abs(Input.GetAxis("Joy" + i + "Y")) > 0.2F)
                Debug.Log(Input.GetJoystickNames()[i] + " is moved");

            i++;
        }
    }
}

脚本字段属性在检视面板中的运用:

// Prints a joystick name if movement is detected.
//如果检测到移动就输出一个控制杆名称
using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    //public修饰的变量可以在检视面板中显示
    //private修饰的不能显示

    //可以显示的GameObject类型的数组 
    //在检视面板中有Size(数组大小)和Element(元素)两个属性
    public GameObject[] gameObject = new GameObject[10];

    //可以显示的一个Rigidbody类型的变量
    public Rigidbody rigidbody = new Rigidbody();

    //不能显示的string类型字符串
    private string NPCName = "Max";

    //可以显示的int类型数值
    public int Score = 11;

    //自动补全 在脚本中定义的初始值会在检视面板中显示
    public int Number { get; set; }

    void strat(){}
    void update(){}
}

三.Time类及单例模式实现

常用Time.deltaTime

在Update/LateUpdate中打印Time.deltaTime时间是不固定的,是一个动态变化值,是前两帧之间时间的差值。

在FixedUpdate中打印Time.deltaTime时间是固定的。

deltaTime() 增量时间

以秒计算,完成最后一帧的时间(只读)。
使用这个函数使和你的游戏帧速率无关。

放在Update()函数中的代码是以帧来执行的.如果我们需要物体的移动以秒来执行.我们需要将物体移动的值乘以Time.deltaTime。

如果你加或减一个每帧改变的值,你应该与Time.deltaTime相乘。当你乘以Time.deltaTime实际表示:每秒移动物体10米,而不是每帧10米。

当从MonoBehaviour的FixedUpdate里调用时,返回固定帧速率增量时间(fixedDeltaTime)。

请注意从OnGUI里你不应该依赖于Time.deltaTime,因为OnGUI可以在每帧被多次调用并且每个调用deltaTime将持有相同的值,直到下一帧再次更新。

C#脚本:

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    void Update() {
        // Move the object 10 meters per second!
        //每秒移动物体10米
        float translation = Time.deltaTime * 10;
        transform.Translate(0, 0, translation);
    }
}

单例模式singleton

单例仅允许被实例一次,这就保证了他在各个程序模块间的唯一性。

//一般使用单例模式时,类名可能为GameManager
//通常用于控制游戏流程 哪个关卡 哪一步 角色信息的记录
//排行榜 积分表等
//在游戏中,单例模式一般在数据的保存和读取时用到
private static ModelLocator instance;
public static ModelLocator getInstance{
    get{
        if(instance==null){
            instance=new ModelLocator();
        }
        return instance;
    }
}

详细的Unity中单例模式的应用,百度”Unity中的单例模式”
二周目的时候详细补充在这

四.GameObject类和对象,多种查找GO的方法

gameObject(g小写)代表当前脚本挂载的游戏对象本身。
若使用this.xxx的话调用的是脚本本身而不是游戏对象

GameObject(G大写)代表游戏对象类。

查找GO并赋值Find族函数:

private GameObject go;

go = GameObject.Find(“Cube”);//根据名字查找对象

go = GameObject.FindGameObjectWithTag(string tag);//根据标签查找

go.activeSelf 游戏物体是否被激活(true or false)

go.activeInHierarchy 游戏物体所处层级的父级是否被激活(true or false)


理解游戏组件和游戏对象的关系

创建一个cube,并不是创建一个cube对象,而是创建了挂载着filter和renderer组件的对象


五.Destroy方法和Transform对象(包括对象移动)

Destroy方法:

销毁一个游戏物体。
Destroy(go);
Destroy(go, 3);//等待3s销毁

Transform对象:

位置transform.position(注意是小写t,是monobehaviour类中的默认字段,表示当前脚本挂在的游戏物体的transform信息)

旋转transform.rotation
缩放transform.scale
向量及运算 Vector3

在Unity中为左手坐标系
这里写图片描述

移动对象:

Transform.Translate

Transform.Rotate

transform.position.x获取x轴坐标

Mathf.PingPong 乒乓
让数值t在 0到length之间往返。t值永远不会大于length的值,也永远不会小于0。
返回值将在0和length之间来回移动。

//Mathf.PingPong 乒乓 例子
using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    void Update() {
    // Set the x position to loop between 0 and 3
    //设置x位置循环在0和3之间
        transform.position = new Vector3(Mathf.PingPong(Time.time, 3), transform.position.y, transform.position.z);
    }
}

六.Lerp插值运算

插值运算:位置Vector3.Lerp 旋转Vector3.Slerp
Quaternion targetrotation = Quaternion.LookRotation(player.position - transform.position);
//根据初始和目标位置计算出对应的旋转角度

插值运算不仅仅可以作为位置、旋转、缩放等计算,还可以做为灯光亮度等的差值计算,也就是说只要是从一个确定状态渐进过渡到另一个确定状态的计算,都可以用到插值运算。
位置插值:三维向量
Vector3 targetpostion = player.position + new Vector3(0, 2.42f, -2.42f);
transform.position = Vector3.Lerp(transform.position, targetpostion, speed * Time.deltaTime);
旋转插值:三维角度
Quaternion targetrotation = Quaternion.LookRotation(player.position - transform.position);
transform.rotation = Quaternion.Slerp(transform.rotation, targetrotation, speed * Time.deltaTime);
灯光亮度插值:浮点值
public float newIntensity = 5;
light.intensity = Mathf.Lerp(light.intensity, newIntensity, speed * Time.deltaTime);
//light.intensity位当前灯光的值
颜色插值:
Color.Lerp(currentColor, newColor, speed * Time.deltaTime);
其他比如Material.Lerp、Mathf.InverseLerp等,可以通过查手册了解。

七.Instantiate实例化prefab对象

所有的C#对象都是继承于object类,包括int、float这些函数类型,当然也包括Monobehaviour、GameObject这些类。

static function Instantiate(original: Object, position: Vector3, rotation: Quaternion): Object;

public GameObject Spawn() 
{
    /* 生成prefab的实例化,因为默认是object类型,所以需要强转为GameObject */
    return GameObject.Instantiate(prefab, transform.position, transform.rotation) as GameObject;
}

其他方法:
GameObject.GetComponent:通过游戏物体获取其组件
CharacterController cc = this.GetComponent();
Animator animator = this.GetComponent();
Component.GetComponent:通过游戏物体的组件获取其其他组件Transform player = GameObject.FindGameObjectWithTag(Tags.player).transform;
PlayerATKAndDamage playerAtkAndDamage = player.GetComponent();
//PlayerATKAndDamage是一个脚本AddForce:添加力AddTurque:添加扭矩

八.协同(协程)以及yield

一般用来在脚本中增加延时效果。因为在Start()或者Update()中是不能直接延时的(WaitForSecond())等待某个操作结束之后再执行代码字符串做为参数:

void Start () 
{
    StartCoroutine("DoSomething", 2.0);
    yield WaitForSeconds (1);//可以在任意位置使用yield语句。yield的返回值控制何时恢复协同程序向下执行。这里等待1s之后才会接着执行下面的语句。
    StopCoroutine("DoSomething");
}

void DoSomething (float someParameter) 
{
    while (true) 
    {
        print("DoSomething Loop");
        // 停止协同程序的执行并返回到主循环直到下一帧.
        yield;
    }
}

IEnumerator做为参数:

IEnumerator Start()
{
    StartCoroutine("DoSomething", 2.0F); //StartCoroutine(DoSomething(2.0F)); 使用IEnumerator做参数不能用StopCoroutine停用。
    yield return new WaitForSeconds(1);
    StopCoroutine("DoSomething"); //请注意只有StartCoroutine使用一个字符串方法名时才能用StopCoroutine停用之。
}

IEnumerator DoSomething(float someParameter)
{
    while (true) {
        print("DoSomething Loop");
        yield return null;
    }
}

开启协同:
StartCoroutine(string methodName):字符串作为参数可以开启线程并在协程结束前终止线程;开启协程时最多只能传递一个参数,并且性能消耗会更大一点
StartCoroutine(IEnumerator routine):只能等待协程的结束而不能随时终止(除非使用StopAllCoroutines()方法)
中止协同:
StopCoroutine(string methodName):中止一个协同,只能终止该MonoBehaviour中的协同程序
StopAllCoroutines():中止所有协同,只能终止该MonoBehaviour中的协同程序
将协同程序所在gameobject的active属性设置为false,当再次设置active为ture时,协同程序并不会再开启。

yield

yiled:和协同密切相关的一个概念,一个协同程序在执行过程中,可以在任意位置使用yield语句。yield的返回值控制何时恢复协同程序向下执行。
yield不可单独使用

  需要与return配合使用,例如:

  1 yield return 0; //等0帧

  2 yield return 1; //等1帧

  3 yield return WaitForSeconds(3.0); //等待3秒

 4 yield return null;//立即返回调用点

  所有使用yield的函数必须将返回值类型设置为IEnumerator类型,例如:
  

IEnumerator DoSomeThingInDelay() {...}

当然,你也可以把Start()返回值定义为IEnumerator类型,那么在Start里面也可以使用yield延时返回,但不推荐这样做:

IEnumerator Start()
{
    StartCoroutine("DoSomething", 2.0F); //StartCoroutine(DoSomething(2.0F)); 使用IEnumerator做参数不能用StopCoroutine停用。
    yield return new WaitForSeconds(1);
    StopCoroutine("DoSomething"); //请注意只有StartCoroutine使用一个字符串方法名时才能用StopCoroutine停用之。
}

你也可以把StartCoroutine和yiled return结合起来使用,保证函数执行顺序,这样调用能保证,init1,init2,init3一个一个的执行,不至于出现后面执行的代码引用一个前面未初始化的变量:

IEnumerator Init()
{
    yield return StartCoroutine(init1());
    Debug.Log("init1 finish");
    yield return StartCoroutine(init2());
    Debug.Log("init2 finish");
    yield return StartCoroutine(init3());
    Debug.Log("init3 finish");
}

IEnumerator init1()
{
    // 模拟初始化
    yield return new WaitForSeconds(2);//
}
IEnumerator init2()
{
    // do somthing..
    yield return new WaitForSeconds(2);//
}
IEnumerator init2()
{
    // do somthing..
    yield return new WaitForSeconds(2);//
}

相关的api总结文章有:
个人开发Unity3d游戏中常用API函数

没有更多推荐了,返回首页