找回密码
 立即注册
查看: 311|回复: 0

Unity C#基础笔记

[复制链接]
发表于 2022-4-13 18:33 | 显示全部楼层 |阅读模式
内容来自《Unity 3D/2D 手机游戏开发:从学习到产品(第4版)》正文和附录A C#语言(比较简陋)、菜鸟教程的C#教程(稍微详细一些)。
(抄书 ing。。。主要记录一些不知道和不熟悉的地方)
Unity的底层使用C++开发,Unity的使用者只能使用脚本进行游戏开发,Unity脚本主要使用C#语言。
C#是“面向对象”编程语言。
Unity 脚本基础

脚本是组件

在Unity中,最基础的游戏单位叫做Game Object(游戏体)。每个Game Object上可以加载不同的Component(组件),包括:Transform(默认),刚体,碰撞体,脚本,音频 ......(很多很多)。


  • Unity 脚本中三个重要的类:
  • MonoBehaviour类:所有Unity脚本的基类,提供大部分Unity功能。如果脚本不是继承自MonoBehaviour,则无法将这个脚本作为组件运行。
  • Transform类:每个Game Object都包含的默认组件,功能:平移、旋转、缩放(这三个在 Inspector 面板可以设置),以及层级面板(Hierarchy)中的父/子关系【子对象随父对象移动】。
  • Rigid Body(2D)类:一种组件,提供所有物理功能。


  • 脚本文件名必须==脚本里的类名,否则将脚本拖到Game Object上时、运行游戏时会报错:The referenced script on this Behaviour ...... is missing!



脚本拖到Game Object上时报的错



这个类名必须和文件名一样

脚本里的常用事件函数

(这里只是简单介绍一下有哪些函数,具体的调用顺序需要进一步学习)
void Awake();  // 实例化后最先执行的函数,只执行一次
void OnEnable();  // 脚本可用后,执行一次;因为可以关闭脚本,所以可以反复执行,在Awake之后执行
void Start();  // 进入Update函数之前执行,只执行一次,在OnEnable之后执行
void Update();  // 程序主循环,每帧执行
void LateUpdate();  // 在Update之后执行
void FixedUpdate();  // 这个函数的执行频率可以自定义,通过 Unity【Project】->【Time】自定义 Fixed Timestep。
脚本序列化

听起来很陌生,其实意思就是:

  • 在脚本里写的 public 变量可以显示在 Game Object 的 Inspector 里,这样就可以在 Inspector 中设置 public 变量的初始值了。
  • 默认只有继承自 MonoBehaviour 的类才能序列化,普通类需要添加 System.Serializable 属性。
例子1:



脚本里的 public 变量



脚本挂到 Game Object 上之后,Inspector 中的脚本组件。为了显示方便,Unity  会自动去掉前缀 m_

例子2:



脚本里的普通类里的 public 变量



脚本挂到 Game Object 上之后,Inspector 中的脚本组件

读取资源、实例化


C# 基础

在C#中,万物皆是类,不存在不属于一个类的函数或变量。
.Net开发框架:微软为Windows操作系统提供的 API (应用程序编程接口),能实现跨平台开发。
Unity的C#和微软.Net家族中的C#差不多。Unity内的C#运行于Mono虚拟机。
Mono虚拟机:一个开源软件平台,提供了与.Net差不多的功能,所以在Unity内使用C#不但能调用Unity引擎本身的功能,还能调用.Net平台中提供的大部分功能;有一些针对Windows平台的专用C#类库,可能无法在Unity中使用。
.NET 类库简介

除了 C# 语言本身,还需要其他项才能构建应用程序:1).NET 运行时,用于最终在计算机上执行代码时托管并管理代码;2)可在应用程序中使用的称为 .NET 类库的功能集合。
.Net 类库是包含成千上万个方法的数以千计的的集合。 这些方法由 Microsoft 创建,并可在应用程序中使用。
类只是一种用于包含方法的容器。 开发者通常将相关方法保留在一个类中。 例如,可从命令行窗口发送或接收信息的任何方法都会被收集到 .NET 类库的 System.Console 类中。
C# 数据类型实际上也是 .NET 类库的组成部分。 为简化我们的工作,C# 掩盖了我们使用的数据类型的真实标识。 但在幕后,数据类型的实现方式与 .NET 类库中每个其他的类都一样。 这也意味着一些有用的方法是内置的,并可用于变量。
可将命名空间视为类型的“姓氏”。 类包含实现类型的代码。 类被组织成不同的命名空间,以防发生命名冲突。 毕竟,拥有上千个类时,可能需要重复使用类名。 命名空间有助于确保任何两个类中不具有相同的全名。
--------------------------
在如此多的类和方法中,如何找到应用程序所需的内容呢?
首先,找到类的一个小子集,你可能会在 C# 软件开发过程中使用该子集。 根据所处理的项目,你会更熟悉 .NET 类库的某些部分,却不太熟悉其他部分。 任何人都不了解所有部分,即使是 Microsoft 工作人员也不了解。
其次,根据需要,使用偏爱的搜索引擎查找博客文章、文章或论坛,其他用户需要执行类似的操作。 第三方源将为你提供线索,甚至提供一些可以尝试的示例代码。
接下来,你可能会花时间来阅读 Microsoft 自己关于特定方法的文档,了解其作用、工作原理、其限制、在什么情况下会引发异常(错误),以及如何缓解这些问题。 文档将成为 .NET 类库的事实来源。 文档团队与 .NET 类库的软件开发者密切合作,确保其准确性。
最后,开始通过小代码项目进行试验,加深你对类和方法工作原理的理解。
在进入不熟悉的领域时,所有软件开发者都遵循类似的过程。 发现的过程虽然充满挑战,但却是令人愉快的。
-----------------------------
若要调用 .NET 类库中类的方法,请采用 ClassName.MethodName() 格式,其中 . 符号是成员访问运算符,用于访问在类中定义的方法,而 () 符号是方法调用运算符。
调用无状态方法时,无需先创建其类的新实例。
在调用有状态方法时,需要创建类的实例,并访问对象的方法。
使用 new 运算符创建类的新实例。
类的实例称为对象。
控制台程序


  • 在 Visual Studio (2019)创建 Console Application(控制台程序)
【问题:这俩有啥区别?】


using System;  // using:在程序中包含 System 命名空间(类库)

namespace _001  // namespace声明:用于防止重名,默认的名称和工程的名称有关。一个 namespace 里包含了一系列类。
{
    class Program  // class声明:类一般包含多个方法(成员函数)。方法定义了类的行为。
    {
        static void Main(string[] args)  // 定义了 Main 方法。Main 方法是所有 C# 程序的入口点。
        {
            // Console(控制台;黑色窗口)是 System 命名空间中的一个类,
            // WriteLine()、ReadKey()是Console类里的方法。
            Console.WriteLine("Hello, world.");
            Console.ReadKey();  // 如果没有的话,控制台闪一下就没了
        }
    }
}
* 预处理

使编译器编译或不编译代码片段。C#的预处理不支持宏(C++预处理支持宏)。

* 类型


  • 值类型【值类型的实例在上静态分配】:内置类型、struct、enum。所有的值类型隐性派生于 System.ValueType。
  • 引用类型【引用类型的对象在中动态分配】:class、delegate(委托)。
  • “引用类型”的值是地址;“值类型”的值是数据本身。
实例化类的对象:在面向对象编程中,通常把“用类创建对象的过程”称为实例化(instantiate)。C++和C#中使用 new 关键字进行实例化。
“引用类型”在没有使用 new 分配内存时,值为 null;使用 new 为其分配内存后,值为“保存对象数据的内存地址”(指针)。
类型转换

整数:现在计算机内存都比较大,一般都可以选择 int 类型。
浮点数:C#默认浮点数为 double 类型,但在游戏中一般用 float 就可以了。float类型的赋值后面要跟一个f。
隐式、显式类型转换:短的可以隐式转换为长的;长的不能隐式转换为短的,需要显式转换。
float somevar = 0.1f;  // float类型的赋值,后面要跟一个f
short x = 10;
            
// 隐式类型转换
// 较短字节的类型可以隐式转换为较长字节的类型,无信息丢失
int y = x;  // short(2 bytes) -> int(4 bytes)
// 长的不能隐式转换为短的
//x = y;  // error CS0266: Cannot implicitly convert type 'int' to 'short'. An explicit conversion exists (are you missing a cast?)
            
// 显式类型转换
x = (short)y;
* 枚举

一种独特的“值类型”。
声明枚举变量:
enum <enum_name>
{
    enumeration list
    /* 枚举列表中的每个标识符代表一个整数值,一个比它前面的符号大的整数值。
    默认情况下,第一个枚举符号的值是 0。*/
};
例子:
using System;

namespace _001
{
    class Program
    {
        // 声明enum
        enum FRUIT
        {
            Apple = 0,
            Banana,  // 值为1
            Cherry,  // 值为2
            Blueberry = 7,
            Pear,  // 值为8
            Orange,  // 值为9
        }

        static void Main(string[] args)  // Main方法,程序入口
        {

            FRUIT fruit = FRUIT.Apple;

            Console.WriteLine(fruit);//输出:Apple
            Console.WriteLine((int)fruit);//输出:0

            // 输出:{0} 参数列表索引为0的值,{1} 参数列表索引为1的值
            Console.WriteLine("{0} = {1}", FRUIT.Banana, (int)FRUIT.Banana);//输出:Banana = 1
            Console.WriteLine("{0} = {1}", FRUIT.Cherry, (int)FRUIT.Cherry);//输出:Cherry = 2
            /*
            Exception thrown: 'System.FormatException' in mscorlib.dll
            An unhandled exception of type 'System.FormatException' occurred in mscorlib.dll
            索引(从零开始)必须大于或等于零,且小于参数列表的大小。
            */
            //Console.WriteLine("{0} = {2}", FRUIT.Banana, (int)FRUIT.Banana));

            Console.WriteLine("{0} = {1}", FRUIT.Blueberry, (int)FRUIT.Blueberry);//输出:Blueberry = 7
            Console.WriteLine("{0} = {1}", FRUIT.Pear, (int)FRUIT.Pear);//输出:Pear = 8
            Console.WriteLine("{0} = {1}", FRUIT.Orange, (int)FRUIT.Orange);//输出:Orange = 9

            Console.ReadKey();
        }
    }
}
C# 面向对象【补充】

(基本的面向对象知识记在“(C#)设计模式 笔记 - 面向对象基础”里面了)
! 静态构造函数

(来自《剑指Offer(第2版)》)


  • 静态构造函数特点:在“类型第一次被使用时”,由“运行时”自动调用,而且保证只调用一次。
  • 静态构造函数:先初始化类的“静态成员变量”,再执行函数体内的语句。
  • 普通构造函数:先初始化类的“普通成员变量”,再执行函数体内的语句。
例子:
// Program.cs
using System;
namespace _001
{
    class A
    {
        public A(string text)  // 普通构造函数
        {
            Console.WriteLine(text);
        }
    }
    class B
    {
        static A a1 = new A("a1");  // 静态成员变量
        A a2 = new A("a2");  // 普通成员变量(私有)
        static B()  // 静态构造函数
        {
            a1 = new A("a3");
        }
        public B()  // 普通构造函数
        {
            a2 = new A("a4");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            B b1 = new B();
            B b2 = new B();
            Console.ReadKey();
        }
    }
}
这个程序执行时发生了什么:
B b1 = new B();
第一次使用 class B,先调用静态构造函数 static B()。
静态构造函数:先初始化类的“静态成员变量”,static A a1 = new A("a1");,再执行函数体内的语句,a1 = new A("a3");
执行该语句B b1 =new B();,执行普通构造函数 public B()。
普通构造函数:先初始化类的“成员变量”,A a2 = new A("a2");,再执行函数体内的语句,a2 = new A("a4");
B b2 = new B();
执行该语句B b2 =new B();,执行普通构造函数 public B()。
普通构造函数:先初始化类的“成员变量”,A a2 = new A("a2");,再执行函数体内的语句,a2 = new A("a4");
因此该程序的输出为:
a1
a3
a2
a4
a2
a4
属性、访问器

例子:
using System;

namespace _001
{
    public class Player
    {
        string m_name = "";  // 不加访问修饰符,默认为private
        public string Name  // 声明属性
        {
            set { m_name = value; }  // set访问器
            get { return m_name; }  // get访问器
        }

        int m_life = 100;
        public int Life
        {
            get { return m_life; }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Player player = new Player();  // 默认构造函数(?)

            // error CS0122: 'Player.m_name' is inaccessible due to its protection level
            //player.m_name = "Player1";

            player.Name = "Player1";
            Console.WriteLine("{0}", player);//输出:_001.Player
            Console.WriteLine("{0}: {1}", player.Name, player.Life);//输出:Player1: 100
            Console.ReadKey();
        }
    }
}

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-30 09:34 , Processed in 0.105700 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表