初识Unity(十步做好你的第一个unity小游戏)

Posted Adamwen520

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初识Unity(十步做好你的第一个unity小游戏)相关的知识,希望对你有一定的参考价值。

一、unity的整体认识

1、创建项目工程文件以及unity布局介绍

(1)打开unityhub,新建项目工程(Project)选择保存路径。

(2)unity的多种布局(窗口的排列)。

认识了tall布局下各个view的功能,四个开发视图(Scene,Project,Hierarchy,Inspector)以及一个game视图。

Scene(场景)视图:显示你当前的场景有哪些可操作的游戏物体,也就是你开发该场景的界面。

Project(资源)视图:存放你要用到的资源,包括音乐、材质、场景等等你要用到的资源。

Hierarchy(层级)视图:列出当前场景包含了哪些东西(把这些东西称为游戏物体)。

Inspector(检视)视图:显示我们所选择东西的属性的。

game(预览)视图:预览你的游戏运行。

2、场景、游戏物体和组件的概念和关系。

场景->游戏物体->组件

场景:一个游戏至少由一个场景组成,游戏很大的话分场景去做结构就可以变清晰。

游戏物体:当你打开一个场景就可以看到这个场景有哪些游戏物体,在Hierarchy视图或Scene视图中,你可以创建、删除、修改当前场景的游戏物体。(经常用到的快捷键需掌握:del删除、F2重命名等等)

组件:选中物体后在Inspector视图上会显示该物体所包含的组件以及各个组件拥有的属性。

场景由游戏物体组成,游戏物体由组件组成,组件包含了属性。通过写脚本和代码来控制不同组件的属性改变,来达到目的。

3、unity中的基本模型和场景操作。

我们在做游戏时会用到很多角色以及其它物体的模型,这时候一般就需要美工在其他软件上做出来我们使用。当然unity也有很多自带的基本模型可供我们使用,在Hirerachy视图中右键便可直接添加各种模型。

 创建一个模型后,便会在左侧Scene视图中显示。

场景的观察方式:Persp(近大远小)透视视野、Iso(长度相同)平行视野。

ISO和Persp操作方式有点不同,我习惯用ISO。 

接下来你就可以对他进行操作:

双击他的名称或者选中该模型按F:聚焦 

鼠标滚轮滑动:放大或缩小界面

鼠标中键:平移视图

鼠标右键:围绕物体旋转视角

4、unity中的坐标系。

3d游戏我们用直角坐标来确定物体在场景中的位置,我们可以在组件Transform的Position属性中修改物体的坐标。

当把一个物体拖动到另一个物体下面,该物体的坐标便是以另一个物体为原点。

世界坐标:相对于整个世界的中心点的坐标。

局部坐标:相对于上一级物体的坐标。

Transform右边有个设置按钮里面的Reset表示初始化。

5、unity中对游戏物体的常用操作。

Q工具:小拖手=鼠标中键

移动Position:W工具

旋转Rotation:E工具

改变大小(长宽高)Scale:R工具

———————————————————————————————————————————

对于unity的各个部分的功能以及操作,我们不必像字典内容一样一一背下来,我们只需要从做一些小项目开始,当你遇到一个知识点,就去了解一个,遇到一个就想方设法吃透它,因为你做的项目需要你用这个知识点,才能继续下去,这样子比你之前去背,要有趣和你对它的熟悉程度要大得多。

下面我们通过一个简单的小游戏制作,了解unity基础操作功能,同时收获一点成就感。

二、第一个游戏:Roll A ball。

1、Roll A Ball游戏介绍。

首先我们要做一个游戏,就要先清楚他的游戏的核心形式,以及游戏场景。在roll a ball 这个游戏中,我们的核心形式就是:要通过控制小球滚动来吃到所有的“食物”。游戏场景是一个有界的平台。所以我们要先创建出我们的场景和操作对象,再通过脚本来实现他们之间的操作。

2、初始化游戏环境。

(1)创建工程文件

        创建一个工程命名为Roll A Ball,选择3D。

(2)创建游戏场地(地面)和角色(小球)

        创建一个场景,放在Project视图里的Scenes文件下,再在Hierarchy视图里右键选择 3D Object创建地面(Plane)和小球(Sphere),按下F2可以重命名。

        初始化小球和地面的坐标,使小球在地面上。   但我们发现小球有一点陷入,使用W工具改变小球y坐标使小球在地面上方。

      

 根据自己想要的大小,可以更改小球或者地面的Scale达到合适的大小。我们也可以通过Material来改变场地的材质。

 如:把添加的Material中的color属性换个颜色,然后把Material拖到Scene视图中的地面上,地面的颜色就会改变。

3、刚体介绍和脚本的创建。

(1)刚体组件的介绍

        刚体组件可以让物体拥有重力,选中物体后在右侧Inspector视图下方添加Rigidbody组件,点击运行,物体就会从高处落下。

(2)脚本的创建

        我们现在就可以通过写脚本来控制刚体组件的属性来控制小球的移动,下面我们先学习脚本的创建。

在Project视图中新建一个文件夹Script来存放脚本,如图中右键选择C# Script,然后把它添加到小球的组件中。

 选中小球(图中的player),把下方新建的脚本拖入右侧,现在我们就给小球创建了一个空的脚本了。

4、通过代码控制移动以及如何通过键盘控制小球移动。

(1)通过代码控制小球移动。

双击Project中要编写的脚本,自动打开编写代码环境,我这里是vs2019。

这里一共由三行代码实现小球的运动。

private Rigidbody rd;        //定义一个rd来接收物体的刚体属性;

rd=GetComponent<Rigiidbody>();       //把物体的刚体属性赋值给rd;

rd.AddForce(new Vector3(1,0,0));       //引用rd中的一个AddForce方法给指定向量上一个力;

这样我们就可以利用刚体给小球一个x方向上大小为1的力了,更改new Victor3向量的方向即可更改小球运动方向。

(2)通过键盘控制小球移动。

既然通过给小球一个方向上施加力可以让小球移动,那么我们就在玩家输入wsad时分别朝四个方向施加力,来达到小球方向改变的目的。

———————————————————————————————————————————

我们先了解几行代码:

unity中Input.GetAxis()的用法

       float x= Input.GetAxis("Horizontal");                //对应键盘上的A键和D键 或←键和→键
       float z = Input.GetAxis("Vertical");                 //对应键盘上的W键和S键 或↑键和↓键
       float h = Input.GetAxis("Mouse X");          //对应X方向上鼠标的移动,在移动设备上也可以这样
       float v = Input.GetAxis("Mouse Y");          //对应Y方向上鼠标的移动,在移动设备上也可以这样
       float m = Input.GetAxis("Fire1");                   //对应鼠标左键或left+Ctrl
       float n = Input.GetAxis("Fire2");                   //对应鼠标右键或left+Alt
       float k = Input.GetAxis("Fire3");                  //对应鼠标中键或left+shift
        float m1 = Input.GetAxisRaw("Fire1");
   input.getAxis()和input.getAxisRaw()的区别
        //input.getAxis()                返回值m从0渐变为1或者-1
        //input.getAxisRaw()         返回值从0变成1或者-1,没有渐变
   ——————————————————————————————————————————

所以我们在脚本中加入两行输入wsad时会返回值的代码,再用变量来接收,然后放入给小球作用力的向量中,这样当玩家按下wsad,就会在对应的方向给小球力,也就达到键盘输入控制小球运动的目的了。代码如下:

 float x= Input.GetAxis("Horizontal");           //对应键盘上的A键和D键 或←键和→键,返回值为小数
 float z = Input.GetAxis("Vertical");             //对应键盘上的W键和S键 或↑键和↓键,返回值为小数

原理就是在接受键盘上的a或←键时返回值从0开始渐变为-1,其他同理。

然后把接收的 h 和 v 放到new Vector3()的 x 和 z 坐标轴上,注意别放错了。

但是我们有个问题,小球这样移动太慢了,我们可以给这个向量乘以一个整型变量来增大它的倍速,并且我们在开头定义一下这个变量,我们就可以在Inpector界面直接修改force的大小。

 

 这样我们就可以通过wsad或者方向键来控制小球任意移动了。

5、控制相机的跟随以及小球移动范围。

每做完一部分内容我们可以运行进行测试,这时我们发现了两个问题:一是小球滚动时镜头不会跟着移动,会滚出镜头外;二是小球会滚到场地外边儿去。

(1)控制相机的跟随

 相机在场景中也相当于一个游戏物体,如图右下方小框就是相机的界面,我们可以拖动相机,让画面照到我们想要的位置。然后我们再写个脚本,让相机与小球的相对距离保持不变,这样小球在移动的过程中,相机也跟着移动,我们就实现了相机的跟随了。

那为何不能把相机拖到小球下面,让相机以小球为原点,这样不是也可以保持距离不变,而且还更简单吗?

确实距离不变了,但是在小球滚动的时候,它的xyz轴也在不断改变方向,这是我们运行起来,相机就会是天旋地转的画面。所以我们只要保持距离不变的跟随,不需要画面跟着转。

 我们在main camera中写一个脚本follow来控制跟随:

主要有三行代码

public Transform playerTransfrom;//定义一个playerTransfrom来获得小球的Transfrom信息。

private Vector3 offset;//定义一个offest来存放相机和小球的相对位置。

offset=transform.position-playerTransform.position;//用offest记录小球和相机的初始相对位置。

transform.position=playerTransform.position+offset;//把小球改变后的位置加上相对距离赋值给相机,这样就是实现了,相机跟随小球一起运动。

直接访问transform,访问的是当前组件的transform(这里也就是相机的transform)。

(2)小球移动范围的控制

        想要控制小球的移动范围,也就是在地面四周加上墙。

        我们创建一个cube,然后通过R工具改变它的大小,再通过E工具改变它的方向,最后通过W工具移到它该去的地方。

6、如何创建可收集的食物以及食物的旋转。

(1)创建可收集的食物。

        可收集的食物的本质是,一个个游戏物体,只不过我们通过代码让它变得被触碰后就被摧毁,得分加一,这样就达到了可收集的目的。要实现被收集我们需要检测到碰撞,所以我们呢先把食物以及食物的状态创建好。

我们创建一个cube,然后把它变为想要的大小,然后用E工具旋转45°(在右侧可以直接输入坐标、旋转角度、大小的值)。

同时我们可以用Material改变一下它的颜色,操作如(二、2.(2))的改变地面颜色,忘了吧QAQ,没事我给你们截了一张图,你们看着应该可以想起。

先在下方创建个Material,再拖到物体上。0.0很简单吧。多复制几个让后放到不同的位置食物就创建好了。

(2)控制食物的旋转。

为了结构清晰我们可以创建一个空的游戏物体,然后把这些食物放到PickUps下面。

现在我们可以通过代码来控制食物旋转,但是我们这样要一个个的去修改,所以我们在这里通过修改标签,就可以达到全部修改的目的。

直接在标签里添加一个脚本控制食物旋转。

这里用到一行代码:transform.Rotate(new Vector3(0,1,0));  //意思是引用transform的Rotate功能绕(0,1,0)旋转。速度为1s60帧,后面我们也可以用代码控制。

运行后便可以达到旋转的目的了,下面我们再来看看碰撞。QAQ

7、碰撞检测。

碰撞检测:检测一个物体与另一个物体有没有发生碰撞。

我们是通过刚体组件中的一个OnCollisionEnter功能来检测的。(关于这些功能在unity手册中有,这些功能需要我们去熟悉。)

 我们在控制小球的脚本中写入OnCollisionEnter来获取碰撞目标的信息:

void OnCollisionEnter(Collision collision)   //接收碰撞到的游戏物体。

        string name=collision.collider.name; //collision.collider获得目标物体上的collider组件信息。

        print(name);输出碰到物体的名字到控制台。(在界面左下角)

 

所以我们可以通过碰撞检测来控制:如果碰撞目标的标签和食物标签一样,我们就摧毁它。

void OnCollisionEnter(Collision collision)

        if(collision.collider.tag=="PickUp")    //判断标签是否相等

        

                Destroy(collision.collider.gameObject);//摧毁游戏物体

        

8、小球通过触发检测吃掉食物。

当小球吃食物时会先发生碰撞,被弹起,食物再消失,这样不太好。食物应该直接消失不应该阻拦小球运动。所以我们在此用到触发检测。

 选中PickUp标签中Box collider的Is Trigger组件,意思是把碰撞器改成了触发器了。

 这时候小球就会穿过食物,但是不消除。因为用的是触发器,碰撞检测无法检测,我们要用触发检测才能够对应触发器的检测。我们现在把触发检测写到脚本中:

 void OnTriggerEnter(Collider collider)    //引用触发检测。

        //如果触发到的物体标签是PickUp,那就摧毁该物体。

        if(collider.tag=="PickUp")

        

                Destroy(collider.gameObject);

        

碰撞会发生实际的物理效果,触发不会。

9、显示分数和胜利检测。

(1)得分显示。

我们是在触发检测中显示吃食物的,所以我们在触发检测中当检测到食物时我们就让得分加一。

声明一个score变量,在collider.tag=="PickUp"时score++。

我们再来看如何在界面上显示score。

在Hierarchy界面中添加一个UI->Text,也就是文本框。然后我们切换到2D模式。

 

左上角点击变为2D模式,然后选择Text点击右边红框然后按住alt点击左上角,这时你的这个文本框就到相机视图的左上角去了。

然后我们进行脚本的编写来控制分数的增加,在player脚本中定义一个text:public Text text。这时我们就可以在player中看到text组件,我们把创建的text拖到这里面,表示我们脚本要引用的text。

 然后我们就可以在脚本中对该text进行操作。

 这行代码表示,给这个text的text属性赋值为score的值。这时我们运行游戏,吃一个食物,score++,text显示的值也加一。

(2)胜利检测。

        在小球吃完食物的时候,我们就在屏幕中显示You Win! 添加文本框过后,可以在右侧对文字的颜色大小进行修改。 

我们想控制小球在吃完时才显示这个文本框,就可以先禁用右上角处的游戏物体是否启用,然后用代码控制在条件合适的时候启用。我们还是在需要用到什么的时候应该先定义:

 我们想对游戏物体进行修改的话,就是用GameObject定义;

只对其组件进行修改,就使用对应组件来定义。

然后我们一共有11个食物,所以当分数为11的时候,我们就激活启用按钮,就可以显示出来You Win!

10、游戏的发布和运行。

好啦,到现在我们就成功做出来了一个简单的小球吃食物的游戏了。接下来我们就把它发布出来,并且成功运行。

在file中点击Build setting。 

选择发布的平台,一般是选的PC端,其他的后面再学。

然后我们选择build然后选择一个文件夹保存。

这样我们就保存到桌面了,这里会有两个文件,一个是数据文件,一个是应用程序缺一不可。

我们直接点exe文件运行游戏。

现在就可以玩你自己做的第一个游戏啦。 

但是这个游戏有个小bug,如果你没选窗口模式,那么你就是全屏无法退出游戏,只有强行结束QAQ。。。

好啦到这里就结束了,初识unity,自己学到了很多,分享给大家,大家是不是也对unity有一些兴趣了呢。

以后会多多分享自己的unity学习经历,希望各位大佬多多指教QAQ。

微软企业库5.0 学习之路——第十步使用Unity解耦你的系统—PART2——了解Unity的使用方法

今天继续介绍Unity,在上一篇的文章中,我介绍了使用UnityContainer来注册对象之间的关系、注册已存在的对象之间的关系,同时着重介绍 了Unity内置的各种生命周期管理器的使用方法,今天则主要介绍Unity的Register和Resolve的一些高级应用。

本篇文章将主要介绍:

1、注册类型同时初始化构造函数参数并重载调用。

2、注册类型同时初始化属性参数并重载调用。

3、延迟获取对象。

4、检索检索容器中注册信息。

 

一、注册类型同时初始化构造函数参数并重载调用

      我们在使用Unity中注册对象之间的关系时,可能对象有相应的构造函数,构造函数中需要传递相应的参数,Unity就支持这样的注册,其主要靠InjectionConstructor这个类来完成,我们首先来看下具体的类构造函数:

public YourClass(string test, MyClass my)
{
    Console.WriteLine(test);
    Console.WriteLine(my.ToString());
}

这个构造函数有2个参数,一个字符串和一个MyClass类对象,相应的可以使用如下代码进行注册:

技术图片
//由于所注册的对象的有带有参数的构造函数,所以注册类型时需要提供相应的参数
//这边采用InjectionConstructor这个类来实现
container.RegisterType<IClass, YourClass>(
    new InjectionConstructor("a", new MyClass()));
Console.WriteLine("-----------默认调用输出-------------");
container.Resolve<IClass>();
技术图片

这样既可完成对象注册的同时对构造函数参数进行注入,此时还有另外一个需求,就是虽然在注册的时候已经对构造函数参数进行了初始化,但是在调用的时候我们想更换原先注册的值,这时应该怎么办?

在Unity中,已经帮我们解决了这个问题,我们可以通过ParameterOverride和ParameterOverrides来实现,其中 ParameterOverride是针对一个参数,而ParameterOverrides是针对参数列表,有关注册参数初始化及参数重载的全部代码如 下:

技术图片
public static void ResolveParameter()
{
    //由于所注册的对象的有带有参数的构造函数,所以注册类型时需要提供相应的参数
    //这边采用InjectionConstructor这个类来实现
    container.RegisterType<IClass, YourClass>(
        new InjectionConstructor("a", new MyClass()));
    Console.WriteLine("-----------默认调用输出-------------");
    container.Resolve<IClass>();
 
    Console.WriteLine("-----------重载后调用输出-------------");
    //以下2种Resolve方法效果是一样的
    //对于参数过多的时候可以采用第2种方法,如果参数仅仅只有1个可以用第1种
    //container.Resolve<IClass>(new ParameterOverride("test", "test"),
    //    new ParameterOverride("my", "new MyClass").OnType<MyClass>());
    container.Resolve<IClass>(new ParameterOverrides()
    {
        {"test","test"},
        {"my",new MyClass()}
    }.OnType<YourClass>());
技术图片

其中需要注意的是:

1、在使用ParameterOverride方法来重载参数时,如果注册的参数是一个具体的对象就需要使用OnType这个扩展方法来指定对应的类型,否则会报错。

2、在使用ParameterOverrides进行重载参数时,可以使用如上面代码的方式进行指定,但是同样需要使用OnType来指定,不过这个的OnType指定的类型是注册的对象类型。

效果图如下:

技术图片

可以看出,其中第一个字符串参数在重载后调用时已经发生了更改。

 

二、注册类型同时初始化属性并重载调用

     这个初始化属性和上面的初始化参数很类似,只不过不同的是,属性的注册初始化是使用InjectionProperty,而重载属性是使用的 PropertyOverride和PropertyOverrides,其使用方法也是相同的,这边就不多介绍了,代码如下:

技术图片
public static void ResolveProperty()
{
    //注册对象关系时初始化对象的属性
    container.RegisterType<IClass, MyClass>(
        new InjectionProperty("Name", "A班"),
        new InjectionProperty("Description", "A班的描述"));
    Console.WriteLine("-----------默认调用输出-------------");
    Console.WriteLine(container.Resolve<IClass>().Name);
    Console.WriteLine(container.Resolve<IClass>().Description);
    Console.WriteLine("-----------重载后调用输出-------------");
    //以下2种写法效果是一样的,同上面的构造函数参数重载
    //var myClass = container.Resolve<IClass>(new PropertyOverride("Name", "重载后的A班"),
    //    new PropertyOverride("Description", "重载后的A班的描述"));
    var myClass = container.Resolve<IClass>(new PropertyOverrides()
    {
        {"Name","重载后的A班"},
        {"Description","重载后的A班的描述"}
    }.OnType<MyClass>());
 
    Console.WriteLine(myClass.Name);
    Console.WriteLine(myClass.Description);
}
技术图片

效果图如下:

技术图片

可以看到2个属性都已经被重载了。

Unity还为我们提供了一个DependencyOverride重载,其使用方法和参数重载、属性重载类似,这边就不演示了,不过需要注意的是DependencyOverride是针对所注册对象类型中所包含的对象类型重载,例如在A类中有构造函数参数是B类,同时也有个属性依赖于B类,当使用了DependencyOverride后,这个A对象原先注册的有关B类的依赖将全部改变。(具体可查看示例代码中的ResolveDependency)

 

三、延迟获取对象

     Unity还有个很不错的特性就是支持延迟获取, 其本质是通过事先建立一个委托,然后再调用这个委托,看下下面的代码:

技术图片
public static void DeferringResolve()
{
    var resolver = container.Resolve<Func<IClass>>();
 
    //根据业务逻辑做其他事情。
 
    //注册IClass与MyClass之间的关系
    container.RegisterType<IClass, MyClass>();
    //获取MyClass实例
    var myClass = resolver();
 
    var resolver2 = container.Resolve<Func<IEnumerable<IClass>>>();
 
    //根据业务逻辑做其他事情。
 
    //注册与IClass相关的对象。
    container.RegisterType<IClass, MyClass>("my");
    container.RegisterType<IClass, YourClass>("your");
    //获取与IClass关联的所有命名实例
    var classList = resolver2();
}
技术图片

这段代码演示了2个延迟获取的方式,都是通过将Func<T>放入Resolve<T>中来实现的,返回的是一委托,这样就可以在实际需要的时候再调用这个委托:

1、第一种是事先通过Resolve<Func<IClass>>(); 来定义获取与IClass关联的对象的委托,然后再注册IClass与MyClass之间的关系,然后再通过resolver(); 来获取。

2、第二种是事先通过Resolve<Func<IEnumerable<IClass>>>(); 来定义获取一个与IClass关联的命名实例列表的委托,然后调用相应的委托就可以一次性获取与IClass关联的所有命名实例。

这2种方式都很好的展示了Unity可以更加灵活的控制对象之间的注册与对象的调用。

 

四、检索容器中注册信息

      当我们在不断使用Unity容器的过程中,我们有时候想看一下容器中到底注册了多少对象,以及各个对象的一些信息,如:什么对象和什么对象关联、具体的注 册名称和使用的生命周期管理器,这些信息都可以在容器的Registrations属性中查看到,在Unity文档中已经有个方法来查看这些信息了,代码 如下:

技术图片
public static void DisplayContainerRegistrations(IUnityContainer theContainer)
{
    string regName, regType, mapTo, lifetime;
    Console.WriteLine("容器中 {0} 个注册信息:",
            theContainer.Registrations.Count());
    foreach (ContainerRegistration item in theContainer.Registrations)
    {
        regType = item.RegisteredType.Name;
        mapTo = item.MappedToType.Name;
        regName = item.Name ?? "[默认]";
        lifetime = item.LifetimeManagerType.Name;
        if (mapTo != regType)
        {
            mapTo = " -> " + mapTo;
        }
        else
        {
            mapTo = string.Empty;
        }
        lifetime = lifetime.Substring(0, lifetime.Length - "生命周期管理器".Length);
        Console.WriteLine("+ {0}{1}  ‘{2}‘  {3}", regType, mapTo, regName, lifetime);
    }
}
技术图片

具体的注册代码如下:

技术图片
public static void RegisterAll()
{
    container.RegisterType<IClass, MyClass>("my");
    container.RegisterType<IClass, YourClass>("your", 
        new ExternallyControlledLifetimeManager());
    container.RegisterType<ISubject, Subject1>("subject1");
    container.RegisterType<ISubject, Subject2>("subject2");
 
    DisplayContainerRegistrations(container);
}
技术图片

效果图如下:

技术图片

可以看到,我在代码中注册的信息都已经很好的反应出来了。

同时如果想查看某个对象是否已经被注册,可以通过container.IsRegistered<T>来验证,这边就不演示了。

 

以上就是本文的所有内容了,主要介绍了Unity的Register和Resolve的一些高级应用,英文好的朋友可以直接查看Unity的官方文档。

 

示例代码下载:点我下载

以上是关于初识Unity(十步做好你的第一个unity小游戏)的主要内容,如果未能解决你的问题,请参考以下文章

怎么把unity游戏界面加到c# 项目中

Unity初识

unity场景制作方式?

P5Unity初识笔记

P5Unity初识笔记

P5Unity初识笔记