从零开始的unity学习笔记

2019-01-05 19:52:34


Day0 Unity的安装

在unity官方网站上注册账号后,点上面的“产品”,选择“Personal个人版”,点“试用个人版”,接受条款。这里建议安装unity hub(就是unity的安装器)。安装unity hub后,在installs——Offical Releases里下载最新的稳定版本。

如果你在此前没装visual studio,此时会顺便给你安装上。

如果你装过,就在visual studio installer里添加“使用Unity的游戏开发”。

等待安装的时候可以看一看官网。官网里的教学视频链接大多数都是youtube,需要科学上网。右上角的Asset Store就是素材商店。合作伙伴里的Vuforia是一个可以配合unity使用的AR工具,用起来很有意思。

unity要求我们使用C#,我们可以在这里学习一个。会C++的应该很容易就能上手,毕竟#就是两个+叠在一起(大雾)。先打开visual studio写个A+B:

using System;

namespace P1001
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] vals = Console.ReadLine().Split();
            Console.WriteLine(Convert.ToInt32(vals[0])+Convert.ToInt32(vals[1]));
        }
    }
}

NOIP2018 D1T1:

using System;

namespace P5019
{
    class Program
    {
        static void Main(string[] args)
        {
            int n = Convert.ToInt32(Console.ReadLine());
            string[] strs = Console.ReadLine().Split(' ');
            int[] vals = new int[n];
            int ans = 0, last = 0, dif = 0;
            for (int i = 0; i < n; ++i)
            {
                vals[i]= Convert.ToInt32(strs[i]);
                dif = vals[i] - last;
                last = vals[i];
                ans += dif < 0 ? 0 : dif;
            }
            Console.WriteLine("{0}",ans);
        }
    }
}

可以先把Learn里面四个基本的交互式教程(Interactive Tutorials)过一遍,熟悉一下基本操作。

Day1 滚个球-1

选择教学项目(Tutorial Projects)里的第三个:“滚个球(Roll-a-ball)”。一开始后面是download,点完后就是一个下载进度条,走完进度就变成start了。

1、调整unity布局

把右上角的布局(Layouts)调成2 by 3(即,左边的2:上边是场景Scene,下边是游戏Game,右边的3依次是层级Hierarchy,项目Project,审查Inspector)。

不建议把语言改成中文,感觉翻译很烂,不仅翻译不完全,还有机翻的感觉。如果一定要改,先在unity hub里单机当前版本后边的三个点,选“Add Component”,拉到最下面的“语言包(language packs)”,下载简体中文。安装后,打开unity,在Edit->Preferences->Languages->Edit Language里选择Chinese。

2、保存场景

File->Save即可保存场景,快捷键ctrl+S。此时会询问保存的目录。在Assets目录下,新建一个目录Scene,在这个目录下保存场景。

比如把场景保存为Sample Scene.unity,存完后我们可以发现,Hierarchy下面的场景名由“Untitled”变成了“Sample Scene”。

3、添加对象

先添加一个平面,小球要在平面上滚。

点GameObject->3D Object->Plane。

或者Hierarchy->Create->3D Object->Plane

我们就有了一个平面。

要更改对象的名称,可以在Hierarchy里点选这个对象,然后F2重命名,或者右键->Rename。

随时记得保存。

4、调整对象的位置

在Hierarchy里点选对象后,Scene里的对象的边缘就会突出显示。

在左上角的工具(Tool)里选择Move Tool。Move Tool可以让我们方便地移动对象。这一排的六个东西,快捷键由左到右依次为“QWERTY”。

然后我们就可以看到三个方向上的坐标轴。

鼠标拖动坐标轴,就可以拉动这个平面。

后面四个工具用法相同,可以试着玩一玩。需要注意的是Scale Tool,由于我们创造的Plane是一个平面图形,因此其在Y轴上的大小是没有意义的。我们向上拉伸Y轴,发现它的大小不变;向下拉伸Y轴,发现这个Plane消失了。

在Scene的右上角找到这个小玩意,点击Y轴对面的这个圆锥,我们就能从下面仰视场景。

然后我们就能看见这个Plane了。当Plane的Y轴方向尺寸为正时,它对上面可见,尺寸为负时,对下面可见。从上图可以看见小玩意的下面还有一个“Iso”,并不知道它是什么单词的缩写。它的意思应该是正交。点一下就会变成“Persp”,是“Perspective(透视投影)”的缩写。点这个就可以实现Scene视图正交与透视的切换。

至于第一个工具,就是拖动镜头的工具。鼠标拖动或键盘上下左右可以实现视角的上下左右移动,鼠标滚轮实现缩放。

此时,我们已经把这个平面进行了一定的扭曲,现在我们来快速把它复原。在Hierarchy里点选对象后,Inspector里会展示它的组成。

在Transform中,右上角有一个小齿轮。点击齿轮,在出现的下拉菜单中点Reset,Position和Rotation都会变为0,Scale会变为默认值。

就是这个效果。

想要修改对象的位置,当然也可以手动修改Transform里的数值。

再用相同的办法创建一个球(Sphere),将位置Reset到原点。按F使Scene里的视角拉到球上。我们发现球与平面重叠了。

Unity内置Sphere类的直径为1,因此我们将其Y坐标增加到0.5即可。

修改后的效果:

5、调整对象的材质

在Assets目录里新建一个Materials目录,用于存放材质。打开Materials,在里面新建一个Material,重命名为Background,作为平面的颜色。选择Background,在Inspector里有一项Albedo(百度了一下,这个词的意思是反照率),点后面的色块,就可以选择材质的颜色。

我们选择三中红:8F000B。

要使用这个Material,只要把它从Project里边拖到Hierarchy里的Plane上就行了。效果是这样的:

6、添加刚体组件

这里的球要会动,能发生碰撞,不会乱飞,我们需要给它添加一个刚体(Rigidbody)组件(Component)。选择Sphere,在Inspector的底部有一个“Add Component”,点击“Add Component”,在搜索框里输入“rigidbody”,选择不带2D的。

By the way,每个对象里的每个组件都可以点右上角小齿轮里的“Move Up”“Move Down”来调整其在Inspector里的位置。

Day2 滚个球-2

1、写一个控制移动的脚本

在Assets里新建一个Scripts目录,在该目录里新建一个名为“PlayerController”的C#脚本。把Project里面的这个脚本拖到Hierarachy里的Sphere上。

双击“PlayerController”,进入Visual Studio编写脚本。首先删掉PlayerController类里的两个默认方法。

更新物体的状态(包括位置状态)有好几个不同的方法,暂时只讨论Update()FixedUpdate()

Update是每一帧执行一次,FixedUpdate是每隔一段时间执行一次。在物理模拟时,我们应该选择FixedUpdate。比如我们同时在卡吧标配和图吧标配上运行“滚个球”,我们钦定每次Update移动1,那么Update就会导致卡吧标配的球滚得更远(每帧移动距离相同,帧数更多),FixedUpdate距离相同(每次更新移动距离相同,每秒更新次数相同)。所以我们在PlayerController类里添加一个FixedUpdate方法。

获取输入需要Input类。Input类中的GetAxis方法可以获取输入的移动。像这样:

float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");

球已经被设置为刚体,对刚体施以力的作用,需要使用Rigidbody.AddForce()。查询文档,它的参数为public void AddForce(Vector3 force, ForceMode mode = ForceMode.Force),或public void AddForce(float x, float y, float z, ForceMode mode = ForceMode.Force)。力的模式采用默认值即可,力的大小是一个空间向量。

Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);

有了力,还要有物体。这段代码已经完成得差不多了,可以参照下面的代码完成剩余细节。

using UnityEngine;
using System.Collections;

public class PlayerController : MonoBehaviour
{
    public float speed;//可以在Inspector中显示、变更,便于调整力的大小
    private Rigidbody rb;

    void Start ()//在游戏的第一帧时调用
    {
        rb = GetComponent<Rigidbody>();//这个脚本和刚体是sphere的两个组件,因此需要获取刚体的引用。
    }

    void FixedUpdate ()//每隔固定时间调用一次
    {
        float moveHorizontal = Input.GetAxis ("Horizontal");
        float moveVertical = Input.GetAxis ("Vertical");

        Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);

        rb.AddForce (movement * speed);
    }
}

然后在visual studio里面点“附加到Unity”,没有CE,可以试一试了。此时在Unity里选择Sphere对象,在Inspector里的Player Controller上出现了一个可供修改的数值:speed,这就是我们代码里的那个public float speed

先调成10试一试。点击unity界面里的Play,按WSAD控制上下左右,就可以让球动起来。

2、写一个控制摄像机的脚本

球是能动了,但是摄像机还不能动。你也许想到了这样的办法:让Main Camera成为Sphere的子对象,摄像机就能和Sphere一起动了。我们可以尝试一下:在Hierarchy里把Main Camera拖动到Sphere的名字上,像这样:

点Play试一下,我们发现摄像机会跟着球一起转。。。

其实原因也很简单,子对象当然会跟着父对象一起转。那我们只能把Main Camera拉出来,写个脚本了。(当然,如果游戏里的对象不会转,我们可以直接把摄像机拖到对象身上)

废话不多说,上代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraController : MonoBehaviour
{

    public GameObject player;//这个摄像机附在player上

    private Vector3 offset;

    void Start()
    {
        offset = transform.position - player.transform.position;//摄像机位置相对于球的偏移值
    }

    void LateUpdate()//LateUpdate是指完成其它的Update后再更新摄像机的位置,每帧执行一次
    {
        transform.position = player.transform.position + offset;//相机的新位置就是球的新位置加上偏移值
    }
}

点“附加到Unity”。在Main Camera的Inspector里找到加进来的脚本。点Player后面的小圈,选择Sphere,这样我们就把摄像机附在了Sphere上。

测试一下,发现摄像机可以跟着球移动了,但是球会滚出去,然后就从Plane上掉下去了。。。

Day3 滚个球-3

1、完善场地

感觉Plane好像有点小,我们先调整一下Plane的大小。把在x与z方向上的大小调成2。

为了把场景围起来,需要在Plane四周加上一圈墙。为了便于管理,我们在Hierarchy中新建一个空对象“Walls”,让其成为四面墙的父对象。Reset一下Walls的位置。

创造一个Cube作为墙。使其成为Walls的子对象,重命名为Wall1,调整一下大小和位置。

复制一个Wall1,更名为Wall2,Position改为$(-10,0.5,0)$

再复制出Wall3和Wall4,调整位置与大小。效果是这样的:

进入Play Mode试一下,发现球可以只在里面动了。

2、设置收集物

我们还要添加一些收集物,小球碰到收集物就可以得分。

先创建一个Cube,Reset一下,把Cube的大小调整为$(0.5,0.5,0.5)$,旋转调为$(45,45,45)$。

Sphere对象和Cube是重合的,看的时候很不方便。我们选择Sphere。把Sphere前的对号取消掉,这样Sphere就不会显示。为了方便管理,将收集物重命名为Pick Up。

我们想让收集物自己转起来,就又需要写脚本了。由于没有施力受力的物理模拟,没有必要进行FixedUpdate(),只要Update就行。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Rotator : MonoBehaviour
{
    void Update()
    {
        transform.Rotate(new Vector3(15, 30, 45) * Time.deltaTime);//钦定一个旋转速度
    }
}

进入Play Mode试一下感觉海星。现在我们要把收集物设为一个预设(Prefab),这样就可以很方便地使用了。

在Project中的Assets目录下新建一个Prefabs目录。把Hierarchy里的Pick Up拖进Project里的Prefabs目录里。

就像刚才的四面墙一样,我们新建一个空对象作为所有收集物的父对象。重命名为Pick Ups,Reset它的位置。

点击坐标轴的Y轴正半轴,让视线向正下方俯视。

像这样:

但是,当我们调整Pick Up的位置时,我们发现它的三个移动方向都是旋转之后的。为了调正旋转方向,点击Scene上面的“Local”,使其变为Global。

然后就变成了:

我们就可以在平面上移动收集物了。要复制对象,Ctrl+C、Ctrl+V固然可以,我们还可以使用Ctrl+D。

放置成这样:

现在我想让收集物换一种颜色。我们在Materials里对之前的Background材质Ctrl+D一下,对新材质重命名为Pick Up。将Pick Up的颜色调为$(0,32,64)$。把Pick Up拖到Hierarchy里的Pick Up上。此时只有一个收集物是蓝的。我们再点一下Inspector里的Overrides,在下拉的菜单中选择Apply All。

就全变蓝了:

把之前取消掉的Sphere再打上勾,进入Play Mode试一试。

Day4 滚个球-4

1、小球与收集物的碰撞

下一样需要考虑的是球与收集物的碰撞。我们打开PlayerController脚本进行修改。

为了了解与碰撞(Collider)相关的信息,选中Sphere,在Inspector里的Sphere Collider上点这个:

点“SWITCH TO SCRIPTING”切换到脚本。

这里我们需要的是一个“OnTriggerEnter”。它会在其他物体碰撞触发器时调用。

我们试着在PlayerController类里加入一个OnTriggerEnter方法。

private void OnTriggerEnter(Collider other)
{
    if(other.gameObject.CompareTag("Pick Up"))//将这个物体的Tag与传入的string比较
    {
        other.gameObject.SetActive(false);//相当于取消Inspector里对象名称前的对号
    }
}

看起来似乎很正确,但是进入Play Mode发现这样布星。为什么呢?

我们添加的方法名为OnTriggerEnter,但是我们看一下收集物的Inspector,我们发现:我们没勾上“作为触发器(Is Trigger)”。

把Prefabs里收集物的“Is Trigger”勾上就行了。经测试,现在小球可以收集方块了。

还有一件有关优化的小事:给Prefabs里的收集物加上Rigidbody,这样收集物就会被识别为动态碰撞器。unity中的碰撞器分为静态和动态。更新静态碰撞器的位置时,unity认为它是静态的,更改会比较少,因此会存入缓存以减少计算。但收集物的位置每时每刻都在转,因此会引起大量的缓存修改,影响性能。

再测试一下,我们发现方块透过Plane掉了下去。。。这是因为方块成为了触发器,受重力作用向下掉,与Plane碰撞。我们把Rigidbody里的Use Gravity禁用掉就可以解决问题。

还有一种解决方法是启用“Is Kinematic”,让物体成为“运动学物体”,即不计算其受力,但仍计算其运动。

2、做一个简单的UI

每收集一个收集物,我们都希望显示得分。也就是说我们要做一个简单的UI。

我们先用脚本记录以下玩家的得分:还是修改PlayerController。修改后的代码是这样的。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{

    public float speed;

    private Rigidbody rb;
    private int count;

    void Start()
    {
        count = 0;
        rb = GetComponent<Rigidbody>();
    }

    void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");

        Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);

        rb.AddForce(movement * speed);
    }

    void OnTriggerEnter(Collider other)
    {
        if(other.gameObject.CompareTag("Pick Up"))
        {
            other.gameObject.SetActive(false);
            ++count;
        }
    }
}

嗯,没有CE。现在我们添加UI。在Hierarchy中添加一个UI-Text,并Reset位置。我们可以在Game中看到“New Text”的字样。

在Inspector里把它的内容改成“Count Text”。这几个字放在屏幕中间似乎不大合适,应该放在左上角。我们调整一下文字的“Rect Transform”。

单击“Rect Transfrom”下面的方块,按住Shift和Alt点击九宫格左上角的那个,即可把文字挪到屏幕的左上角。

颜色有点暗,位置有点偏。为了不侮辱读者的智商就不细说调整方法了。直接看效果:

每次碰到收集物,我们都应该更新得分。

using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;//使用UI,需要使用这个命名空间
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float speed;
    public Text countText;//即左上角的文本

    private Rigidbody rb;
    private int count;

    void Start()
    {
        count = 0;
        rb = GetComponent<Rigidbody>();
        SetCountText();
    }

    void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");

        Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);

        rb.AddForce(movement * speed);
    }

    void OnTriggerEnter(Collider other)
    {
        if(other.gameObject.CompareTag("Pick Up"))
        {
            other.gameObject.SetActive(false);
            ++count;
            SetCountText();
        }
    }

    void SetCountText()
    {
        countText.text = "Count: " + count.ToString();
    }
}

保存,选中Sphere,脚本里的Count Text还是空着的。

把Hierarchy里的Count Text拖到上面,就变成了:

测试一下,效果不错:

我们还希望在收集所有方块后可以有一个提示信息。再新建一个Text,重命名为Win Text,位置居中。再修改一下脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float speed;
    public Text countText;
    public Text winText;

    private Rigidbody rb;
    private int count;

    void Start()
    {
        count = 0;
        rb = GetComponent<Rigidbody>();
        SetCountText();
        winText.text = "";//初始为空串
    }

    void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");

        Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);

        rb.AddForce(movement * speed);
    }

    void OnTriggerEnter(Collider other)
    {
        if(other.gameObject.CompareTag("Pick Up"))
        {
            other.gameObject.SetActive(false);
            ++count;
            SetCountText();
        }
    }

    void SetCountText()
    {
        countText.text = "Count: " + count.ToString();
        if(count>=8)
        {
            winText.text = "You win!";//全部收集,将winText改为You win!
        }
    }
}

效果:

3、添加BGM

在Project的Assets目录下创建一个Audios目录,把已经下载好的音乐拖进去。

在Hierarchy里创建一个Audio Source,把音乐拖进Audio Clip。启用Loop。

进入Play Mode就可以听到BGM了。

4、编译游戏

在File里找到Build Settings。

可以看到unity支持的平台很多。似乎在印象里,unity都是做手游、web小游戏的,其实现在的unity已经越来越强大了。

我们选择默认的PC,Mac&Linux Standalone,把Hierarchy里的场景拖到上面的“Scene In Build”里。

点Build,编译到你想存的地方。编译完成后试玩一下。滚个球教程就到此结束辣!

Day5 MMD4Mecanim-1

1、环境准备

首先当然要有MMD。

MMD4Mecanim下载地址下载MMD4Mecanim的zip。

还要安装Blender或Autodesk Maya。在这个项目里,Blender要方便一些,但我对Maya比较熟,而且Maya的效果较好,所以我使用Maya。

随便找一套MMD模型(.pmx)、一套MMD动作(.vmd)、一套相机文件(.vmd)和一段对应伴奏。

Maya用户还要下载MMDBridge或MMD4Maya。

2、导入插件与素材

新建一个3D项目。

解压MMD4Mecanim。这个插件名字中的Mecanim就是Unity的动画系统(Animation System)。把比较大的那个(如果有两个).unitypackage拖到Assets上,Unity会import这个package。

该点import的时候就点。

提示里面有弃用的API,你就Go ahead。

把存模型的目录拖到Assets目录下,发现每个.pmx的下面自动生成了一个asset。选中对应的asset,Inspector里会弹出一个利用规约,我们全都同意就行。

这个asset有三个参数(如果不点高级模式),第一个和第三个已经自动生成。我们需要填上第二个:VMD。

在Assets目录下新建一个存动作的Animations目录。把.vmd动作文件拉进这个目录。把这个文件拉进第二个参数栏即可。

单击Process后会弹出一个pmx2fbx,把.pmx文件转换为.fbx,然后会自动导入Unity。对于比较精细的模型和性能较弱的电脑来说,这一步可能会耗费很长时间。

我们这个时候可以准备一下相机的导入。首先借助MMDBridge或MMD4Maya将素材导入Maya。(我们只是要借助Maya获得相机的Alembic文件,因此不需要贴图)。

这个时候,模型大概导入完了。现在导入音乐。在Assets里新建Audios目录,把音乐拖进来。

3、让模型动起来

我们把生成的.fbx文件拖到Hierarchy里:

哇,成功了!Play一下试试!

(Miku纹丝不动)

这是因为.fbx只是模型,没有动作。我们点击Project里那个.fbx左边的小三角,发现里面有一个名字很长,结尾是_vmd的文件。把它拖到Hierarchy里的模型上,再Play一次:

动起来了!

怎么配音乐来着?还记得滚个球里面的方法吗?给模型加一个Audio Source。

调整模型大小和相机位置,得到合适的效果,进入Play Mode看一看。

额,头发有点不大对啊。。。

这就需要调整物理了。在Hierarchy中选中模型,找到MMD4Mecanim Model这个Component,进入Physics选项卡。调大Gravity Scale,发现效果好多了。

Day6 Octane渲染器

在尝试的过程中,发现有两个dll一直有问题,后来才发现是因为Octane渲染器需要CUDA,这两个dll应该是依赖某个与CUDA有关的dll。但本人的显卡是RX580,因此本段暂时咕咕咕。比较经济的硬件升级方案是买一块P106矿卡作CUDA计算卡,以原显卡作为主卡,然而我并不想继续折腾了。

Day7 Vuforia

1、环境准备

Day8 MMD4Mecanim-2