unity3d 开发笔记

unity 3d

Unity是一个游戏引擎,包含渲染引擎,物理引擎,碰撞检测,音效,动画效果,场景管理等系统。它的开发效率高、脚本使用C#开发、简单易用、跨平台(可以导出各个平台的程序),别的游戏引擎比如虚幻引擎俗称UE (Unreal Engine,使用C++开发脚本,3A大作首选)。下边放一个Unity软件的大图。

在这里插入图片描述

介绍

  • project面板放游戏资源,hierarcy放游戏对象,inspector显示当前游戏对象和属性信息
  • component组件代表游戏对象的某个功能,所有游戏对象都有transform组件
  • 目录下的assets是最重要的,其他可以不要
  • 直接安装unity就可以,不用安装unity hub
  • ProjectSettings\ProjectVersion.txt 查看项目的unity版本
  • unity package file, unity可以直接导入,类似zip文件,可以右键export package生成,主要使用的unity版本尽量一致

操作

  • 最右上角,面板 layout,选择 2 by 3
  • project面板 右键 one column layout
  • Q + 鼠标左键(按鼠标滚轮),可以移动scene
  • alt + 右键,缩放视图
  • alt + 左(同只按右键),旋转视图
  • 右键 + w a s d q e,场景漫游(不同方向)
  • 选择物体,然后按F,视图以物体为中心来居中,或者在hierarcy双击游戏对象也可以
  • 选择游戏对象,ctrl + d 可以快速复制一个
  • z轴是正对人的放心,x轴是水平方向,y轴是垂直方向
  • 游戏运行时改的值,可以copy component,然后结束调试paste值即可。

视图模式

在这里插入图片描述

视图有两种模式,ISO正交模式(2d效果)和 Pers透视模式(近大远小效果)。

坐标系

  • 世界坐标,全局不变的坐标(只有唯一的原点)
  • 本地坐标,物体自身坐标,随着旋转而变化
    unit使用左手坐标系(左手做成数字8,中指指向自己即可)
    在这里插入图片描述

场景

场景 scene,是相关联的一组游戏对象的集合,比如一个地图或者游戏的某一关,在scenes文件下,后缀名是 .unity 双击此文件可打开项目。新建的游戏对象,默认在当前视图的场景中间。场景默认自带Main Camera,和Directional Light两个东西。

GameObject

游戏对象,hierachy中的每个对象都是GameObject的子类,都有transform属性。Plane只有正面没有反面,quad相似,只不过是竖着的。任何物体都是三角形拼出来的。

PreFab 预制件

如果在Hierachy面板中,要把一个游戏对象变成模板,直接拖到 Project面板即可,修改PreFab的属性就会影响所有的游戏对象。如果要批量创建模型,最好放到预制件里。
在这里插入图片描述

复制参数

在这里插入图片描述
如图,如果要复制这些参数给别的组件使用,可以点这里,选择 copy componnet,
然后在新的组件再点这里,选择 Paste Component Values。

保存参数

在这里插入图片描述
比如,如果要保存这个transform的参数,给别的项目使用或者给别的组价使用,可以点这里

在这里插入图片描述
点save,会保存为一个文件

在这里插入图片描述
可以在别的组件直接使用这个transform的参数,而不用手动输入了。

Scripts

创建脚本,不用的方法,最好删除。
每个脚本的执行没有顺序的,脚本可以设置执行优先级,选中脚本,然后点击右侧按钮。
在这里插入图片描述
在这里插入图片描述
可以选择一个脚本作为主控脚本。
脚本的参数,要 public才行,也可以添加 tooltip
在这里插入图片描述

public class Planet : MonoBehaviour
{
    
    
  [Tooltip("帧数")]
  public int frame = 60;

  void Start()
  {
    
    
    Application.targetFrameRate = frame;
  }

  void Update()
  {
    
    
    this.transform.Rotate(0, 30 * Time.deltaTime, 0);
  }
}

参数设置优先级, 默认 < 编辑器 < Awake < start

mesh

网格,mesh filter可以改变物体的形状。
在这里插入图片描述
Mesh Renderer 渲染物体,可以改变材质,两个组合在一起才能显示物体。可以创建空物体,自行添加,这两个组件,让物体显示。
在这里插入图片描述

群组父子结构

如果要组合两个对象,在hierachy面板,一个拖到另一个就可以。也可以创建几个空模型作为父对象,其他的拖进来。这时候子模型的坐标就是相对坐标了,相对父模型的坐标,可以使用transform组件的reset功能重置,会更好。如果后续模型更新,如果把组件都挂在模型上,就要重新替换,所以建议新建一个空物体作为父模型,把所有行为都给父模型即可。

材质 material

物体的质地,比如色彩、纹理、透明度等,实际就是shader的实例。
纹理(texture)就是附加到物体表面的贴图。材质要给到 Mesh Renderer
在Assets目录下新建Textures目录,放置贴图图片,可以直接拖到game object上,unity会自动生成一个材质。
在这里插入图片描述
Albedo
基础贴图,决定物体纹理和颜色

Rendering mode

材质的渲染模式 ( Rendering mode ) 默认是 opaque(不透明的),fade(渐变,淡入淡出)

cutput (镂空,透明通道去掉,只剩下不透明的)
在这里插入图片描述

transparent(透明的,需要设置albedo的颜色的a值)
在这里插入图片描述

Metallic
使用金属模拟外观

Specular
镜面反射

Smothless
光滑度,设置物体表面光滑程度

Normal Map
法线贴图,物体表面凹凸程度

Emission
自发光,控制物体表面自发光颜色和贴图。

shader

材质的本质是shader的实例,shader是专门用来渲染3d图形的技术,可以使纹理以某种方式展现,就是一种嵌入到渲染管线的程序,控制GPU运行效果的算法。材质的属性和设置,是由shader决定的。

在这里插入图片描述

场景下的绘图模式

Shaded着色模式(默认模式)所有游戏对象的贴图都正常显示
Wireframe网格线框显示模式以网格线框形式显示所有对象
Shaded Wireframe着色模式线框以贴图加网格线框形式显示对象
Shadow Cascades阴影级联以阴影方式显示对象
Render Paths渲染路径显示模式以渲染路径的形式显示
Alpha ChannelAlpha通道显示以灰度图的方式显示所有对象
Overdraw以半透明方式显示以半透明的方式显示所有对象
MipmapsMIP映射图显示以MIP映射图方式显示所有对象

光照

平行光,像太阳,从一个方向过来照亮全景
点光,一个点发射光线,并减弱
聚光,某个方向照射,有范围限制
范围光,一个区域内的光,没有方向
实时光照和烘焙光照,后者就是开发时把光照计算好,在游戏运行时,直接使用图片产物,提升性能。
一般场景为了真实,会有几个光源,因为有漫反射的存在,没有绝对黑的物体的面。
很多游戏没有阴影,因为实时渲染影响非常消耗资源。
比如范围灯光,看不出效果,可以烘焙看一下,windows -> rendering -> lighting
在这里插入图片描述

粒子系统

可以实现硝烟,火焰,雪花,水汽,爆炸效果。右键Effects -> Particle System来创建。也可以从网上下载现成的使用。

渲染管线

渲染管线执行一系列操作来获取场景的内容,并将这些内容显示在屏幕上。

在 Unity 中,可以选择不同的渲染管线。Unity 提供了三个具有不同功能和性能特征的预构建渲染管线,您也可以创建自己的渲染管线。

渲染3D物体需要考虑物体着色、光照计算、环境光、阴影计算,、传递哪些数据、 自定义的Shader等,渲染管线,就是定义了处理这些的原理原则,在这些原则下渲染游戏场景中的物体。

Unity的渲染管线分为3大类渲染管线: 1是向前渲染管线,2是延时渲染管线,3是可编程渲染管线; 1和2是Unity引擎内置的渲染管线,来处理整个场景的绘制和渲染相关机制,3允许开发者自己定义,这样能获得更好的灵活性和效率。

比如向前渲染管线,每个重要光源,都要经过一次Pass,来渲染一次物体,不重要的光源,在ForwardBase里面计算等,而延时渲染就是先把所有的光照计算出来,最后再渲染(“延时”)。不同的渲染管线,对应的技术处理的策略不一样。

1、默认渲染管线。默认渲染管线就是你不装LWRP、UPR、HDRP这些管线,Unity本来就内置的管线。这个管线只是Unity对底层渲染API(如OpenGL等)做了简单封装搞出来的,并且当时Unity并没有去考虑这个管线的可定制性。尽管可以用CommandBuffer和一些渲染时的回调来对这个管线的渲染流程做一些修改,但个人感觉都是些比较骚的操作,搞出来的东西稳定性不是很好。所以默认渲染管线的问题就是死板。

2、SRP。即可编程渲染管线。这玩意相当于是对默认渲染管线又做了层封装,并暴露给用户。这大大提高了渲染管线的可定制性。SRP把渲染管线细分成一个个模块和过程,你只需要按照自己的需求去组装和重写即可。比如我用SRP可以先渲染出场景中一部分物体,然后做个后处理,再渲染剩下的物体,在做其它后处理等等。

2、LWRP。这玩意就是Unity用SRP给你写的一个渲染管线。如果用户觉得自己没有太多的渲染管线定制需求,也不想花很多时间在管线的编写上的用户,那直接就可以用这个LWRP来替代默认渲染管线。并且LWRP也具有一定的可定制性。

3、URP。就是LWRP的升级版。给LWRP改改BUG、优化些体验、用些底层新特性、提高渲染质量这样。

脚本开发

  • Unity是一个Component-Based的引擎,所有物体都是GameObject。
  • Time,Input,Physics都是Unity中的全局变量。
  • MonoBehaviour 是 Unity 中所有脚本的基类。

基础和总结

  • window -> general 显示 console 控制台
  • 关于注释, /// 会被编译,// 不会。所以使用///会减慢编译的速度(但不会影响执行速度)且在其它的人调用你的代码时提供智能感知。
  • #region 是 C# 预处理器指令。就是将复杂的代码块折叠,是界面看起来整洁一些。
  • 一般使用一个空物体,标记一个坐标。

向量

一般使用Vector3,两个向量可以直接相加减。

Vector3 v = new Vector3(0,1,2);
// 向量长度
v.magnitude;

单位向量,长度为1的向量。

// 标准化v,返回v1是单位向量
var v1 = v.normalized;

常用的常量比如 vector3.forward 代表 (0,0,1)等
求两个向量的距离vector3.Distance

GameObject和Component

GameObject是游戏场景中真实存在的,而且有位置的一个物件。Component附属于GameObject,控制GameObject的各种属性。GameObject是由Component组合成的,Component的生命周期和GameObject息息相关。调用此GameObject的Destroy方法,它的子对象和对应的所有Component都会被销毁,但也可以一次只销毁一个Component。常用的组件如下表所示

Component 作用
RigidBody 刚体 使物体能在物理控制下运动
Collider 碰撞器 和RigidBody刚体一起使碰撞发生,没有Collider,两个碰撞的刚体会相互穿透
Renderer 渲染器 使物体显示在屏幕上
AudioSource 音频源 使物体在scence场景播放音频
Animation 动画
Animator 动画控制器

脚本也是组件,因此能附到游戏对象上。常用的组件可以通过简单的成员变量获取,附在游戏对象上的组件或脚本可以通过GetComponent获取。

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    
    
    void Awake() {
    
    
        transform.Translate(0, 1, 0);
        GetComponent<Transform>().Translate(0, 1, 0);
    }
}

获取某个组件,可以直接把引用给脚本即可

语法结构

  • 文件名必须和类型相同,首字母大写
  • 脚本必须附加到物体上才可以执行
  • 附加到物体上的类必须继承 MonoBehaviour
  • 删除类中没用的空方法
public class WeatherParticle : MonoBehaviour

下边是一个脚本的demo

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Ball : MonoBehaviour
{
    
    
    // 游戏开始运行的时候执行,适合做初始化
    void Start()
    {
    
    
        Debug.Log("组件执行开始!");
        transform.position = new Vector3(1, 2.5f, 3);
    }
 
    // 每帧都会执行,不同设备的频率不一样
    void Update()
    {
    
    
        Debug.Log("当前游戏进行时间:" + Time.time);
        // 每次帧移动 1.5 
        transform.Translate(1.5f, 0, 0)
        // 如果要保证每秒移动距离相同,Time.deltaTime是两个帧的间隔
   		transform.Translate(1.5 * Time.deltaTime, 0, 0)
    }
}

获取位置

// 获取纵轴输入和横轴输入
// wsad和上下左右键的输入
float v = Input.GetAxis("Vertical");
float h = Input.GetAxis("Horizontal");
Debug.Log("纵轴输入"+v+"横轴输入" + h);
transform.Translate(0, h * 3 * Time.deltaTime,  v * 3 * Time.deltaTime);

触发和碰撞

// 1. 增加一个立方体,在 box collider上勾选 is trigger
// 2. 给球体增加一个刚体组件,(add component -> pyhsicis -> rigidbody)勾选 
// 3. 使用前边的小球撞立方体,给立方体挂一个脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Coin : MonoBehaviour
{
    
    
    //触发开始事件OnTriggerEnter
    private void OnTriggerEnter(Collider other)
    {
    
    
        Debug.Log(other.name + "碰到了");
    }
    private void OnTriggerStay(Collider other)
    {
    
    
        Debug.Log(other.name + "碰撞持续中");
    }
    private void OnTriggerExit(Collider other)
    {
    
    
        Debug.Log(other.name + "碰撞结束");
    }
}

编译过程

源代码 - CLS - 中间语言 - Mono Runtime - 机器码

修改默认cs模板

打开编辑器的目录,比如我的是
D:\apps\unity-editor\2021.3.29f1c1\Editor\Data\Resources\ScriptTemplates
打开 81-C# Script-NewBehaviourScript.cs.txt 修改即可

脚本生命周期(消息方法)

也叫必然事件/消息,脚本从唤醒到销毁的过程。附加到游戏对象上,控制游戏行为。
在class中声明的可见的成员变量(private不可见),在unity软件中可见也可以修改,并且这个优先级高,以为在unitu中这是一个对象。不要在脚本中写构造函数

public class Demo1 : MonoBehaviour
{
    
     
	// 加上这个,虽然是public在unity也不可见
    [HideInInspector]
    public float speed = 10;
    
    // 虽然私有,但是在unity可见
    [SerializeField]
    private float speed1 = 10;

	// 字段定义数值范围 
	[Range(0, 100)]
    public float speed2 = 10;

  	// 执行时机,渲染帧执行,每次渲染时执行,执行间隔不固定和设备性能和渲染量有关
    // 适用性: 处理游戏逻辑
    void Update()
    {
    
    
        float v = Input.GetAxis("Vertical");
        float h = Input.GetAxis("Horizontal");
        transform.Translate(h * speed * Time.deltaTime, 0, v * speed * Time.deltaTime);
    }


    // 固定时间更新,
    // 适用性:适用游戏对象做物理操作,比如移动等,默认为0.02s,不会受到渲染影响
    private void FixedUpdate()
    {
    
    


    }

    // 延迟更新,适用于跟随逻辑
    private void LateUpdate()
    {
    
    

    }



    // 开始时被调用
    // 创建游戏物体,立即执行
    // 在脚本被禁言仍然可以执行
    private void Awake()
    {
    
    
        Debug.Log("awake" + Time.time);
        print("lll");
    }

    // Awake之后执行
    // 创建游戏物体,脚本启用才执行
    // 如果此脚本被挂在多个游戏对象上,先执行所有的awake,再执行所有的start
    // this.name 游戏对象的名字
    private void Start()
    {
    
    

        print(this);
        speed = 10;
        Debug.Log("Start" + Time.time + this.name);

        // unity内置随机数
        int b = Random.Range(0, 100);

        int a = 0;

        while(a < -1){
    
    
            //我们将obj1初始化为一个Cube立方体,当然我们也可以初始化为其他的形状
            GameObject obj1 = GameObject.CreatePrimitive(PrimitiveType.Cube);
            //设置物体的位置Vector3三个参数分别代表x,y,z的坐标数
            obj1.transform.position = new Vector3((float)(1 +  a * 1.3),1,1);
            //给这个创建出来的对象起个名字
            obj1.name = ("dujia" + a);
            a++;
        }


		//设置物体的tag值,在赋值之前要在Inspector面板中注册一个tag值
		//注册tag值得方法,用鼠标选中摄像机对象在Inspector面板中找到tag,选addtag
		// obj1.tag = "shui";
		//设置物体贴图要图片文件放在(Resources)文件夹下,没有自己创建
		// obj1.renderer.material.mainTexture = (Texture)Resources.Load("psb20");

    }

    private void OnMouseEnter() {
    
    
          Debug.Log("OnMouseEnter");
    }

    private void OnMouseExit() {
    
    
          Debug.Log("OnMouseExit");
    }

    private void OnMouseDown() {
    
    
        // print(this.activeSelf);
        print("OnMouseDown");
    }
 
    private void OnMouseUp() {
    
    
          Debug.Log("OnMouseUp");
    }

    // 当物体在相机不可见时
    private void OnBecameInvisible() {
    
    
        
    }

    // 当物体在相机可见时
    private void OnBecameVisible() {
    
    
        
    }

    // 销毁时
    private void OnDestroy() {
    
    
        
    }

    // 程序结束时
    private void OnApplicationQuit() {
    
    
        
    }
}

在这里插入图片描述

消息调用

比如点击屏幕,让cube移动,cube上有个脚本 cube.cs,上面有move1的方法,另外一个脚本 control.ts 有cube这个游戏对象的引用,那么就可以直接这么写

cube.sendMessage("move1");

如果没有这个方法名会报错。

debug

  • 这两种log是等价的,在继承monohavior的情况下,测试环境可以加,生产环境需要删除。
  private void Awake()
   {
    
    
       Debug.Log("awake" + Time.time);
       print("123");
   }
  • 使用类的成员变量,在inspector面板可以实时看到这个数据的变化。

重要的类

在这里插入图片描述

Component

public class ComDemo : MonoBehaviour
{
    
    
    // OnGUI是Unity中通过代码驱动的GUI系统
    // 主要用来创建调试工具、创建自定义属性面板、创建新的Editor窗口和工具达到扩展编辑器效果
    private void OnGUI() {
    
    
        // 左上角创建一个按钮
        if(GUILayout.Button("click")){
    
    
            // 点击按钮的行为
            print("ok");
            this.GetComponent<MeshRenderer>().material.color = Color.blue;
            // 自定义颜色
            // this.GetComponent<MeshRenderer>().material.color = new Color(0.3f, 0.4f, 0.6f, 0.3f);

            // 获取所有的组件
            var all = this.GetComponents<Component>();

            foreach (var item in all)
            {
    
    
                print(item.GetType());
            }

            // 深度优先搜索所有的子组件
            // var allAndChildren = this.GetComponentsInChildren<MeshRenderer>();

        }
    }
}

物体移动

public class Move1 : MonoBehaviour
{
    
    
    private void Start() {
    
    
        // 建议unity一个固定的帧率,不一定有效
        Application.targetFrameRate = 60;
    }

    void Update() {
    
    
    	// 直接给一个新的位置
        var ps = transform.localPosition;
        ps.x += 1.2f * Time.deltaTime;
        transform.localPosition = ps;
        print(Time.deltaTime);
        
   		/**
         相对移动,传入位移增量
        */
        // 自身坐标系z轴每次移动1米
        this.transform.Translate(0, 0, 1);
        // 世界坐标系
        this.transform.Translate(0, 0, 1, Space.World);
    }
}

物体旋转

using UnityEngine;

public class RotateDemo : MonoBehaviour
{
    
    
    void Start() {
    
    
        Application.targetFrameRate = 60;
        // 官方推荐,使用欧拉角设置旋转
        // transform.localEulerAngles = new Vector3(0, 45f, 0);
        // transform.rotation = Quaternion.Euler(3.0f, 1.2f, 1.0f);
        
    }

    void Update() {
    
    
        // 使用欧拉角设置
        // var angle = transform.localEulerAngles;
        // angle.y += 30 * Time.deltaTime;
        // transform.localEulerAngles = angle;
        
        // 自身坐标系的y轴旋转10度
        this.transform.Rotate(0,30 * Time.deltaTime,0);
        // this.transform.Rotate(0, 10, 0, Space.World);
    }
}

Transform

public class TransformDemo : MonoBehaviour
{
    
    
    void OnGUI()
    {
    
    
        if (GUILayout.Button("transform"))
        {
    
    
            // 每个子物体的变化组件
             foreach (Transform c in transform)
             {
    
    
                 print(c.name);
             }


            // 世界坐标系坐标位置
            print(this.transform.position);
            // 相对于父轴心点的位置
            print(this.transform.localPosition);
            // 相对于父的缩放
            print(this.transform.localScale);

            // 围绕世界坐标点旋转,y轴,1度
            this.transform.RotateAround(Vector3.zero, Vector3.up, 1);

            // 获取父物体变换组件
            this.transform.parent
            // 是最高层级的transform
			this.transform.root
			
            // 设置父物体变换组件
            this.transfrom.setParent(this.transform.root, true)

            // 根据名称查找子物体的transform对象
            Transform tf1 = this.transform.Find("trans1");

            // 查找孙子路径
            Transform tf11 = this.transform.Find("trans1/transf2");

            int count = this.transform.childCount;
            print(count);
            for(int i=0;i < count; i++){
    
    
                print(transform.GetChild(i));
            }
        }
    }
}

gameObject的激活状态

在这里插入图片描述
勾上就是激活,不勾上就是不激活状态,可以用setActivate来设置,不激活就是不在场景显示了。this.activeSelf是自己的激活状态,但是如果父物体不是,那也不是。所以使用 activeInHierarchy来判断最终状态。

public class GameObject1 : MonoBehaviour
{
    
    
    void OnGUI()
    {
    
    
        if (GUILayout.Button("GameObject1")) 
        {
    
    
            print(this.gameObject.activeSelf);
            print(this.gameObject.activeInHierarchy);
            
            // this.gameObject.SetActive(false);

            // 所有的组件不能new
            Light light = this.gameObject.AddComponent<Light>();
            light.color = Color.red;
            light.type = LightType.Point;

            // 使用标签或者游戏物体列表
            GameObject[] gbs = GameObject.FindGameObjectsWithTag("Player");
            GameObject gb = GameObject.FindWithTag("Player");
            print(this.gameObject.name);
            
            // 5s后销毁游戏对象
			Destroy(this.gameObject, 5);

        }
    }
}

InputManager(旧输入系统)

可以获取用户的输入事件

using UnityEngine;

// 这个就会在 add component下显示一个Demo2222的文件夹,名字是input111
// 用于定义在unity中的别名
// 这种写法是C#的特性
[AddComponentMenu("Demo2222/input111")]
public class InputDemo : MonoBehaviour {
    
    
  void Start() {
    
    
    
  }

  void Update() {
    
    
    if (Input.GetKey(KeyCode.A)) {
    
    
      print("Pressed A.");     
    }
    
    if(Input.GetKeyDown(KeyCode.W)){
    
    
      print("Pressed Down w.");
    }
    
    // 鼠标被按下,不停的出发
    if (Input.GetMouseButton(0)){
    
    
      print("Pressed left click.");
    }
    
    // 用户按下了左键,只触发一次
    if (Input.GetMouseButtonDown(0)){
    
    
      print("Pressed left down.");
    }
    
    // 用户抬起了左键,只触发一次
	if (Input.GetMouseButtonUp(0)){
    
    
      print("Pressed left Up.");
    }

    if (Input.GetMouseButton(1)){
    
    
      print("Pressed right click.");
    }

    if (Input.GetMouseButton(2)){
    
    
      print("Pressed middle click.");
    }
    // Fire1的定义如下所示
    if (Input.GetButton("Fire1")) {
    
    
       print("fire1");
    }
    // 这种down的执行的次数少
    if (Input.GetButtonDown("Fire1")) {
    
    
      print("fire1 down");
    }
    
    // 参数值也是标准输入选的,在setting里
    // print(Input.GetAxis("Horizontal"));
    // 没有中间过程的值,比如按下 w使得 0 - 1 上边方法可能有小数,这个要么0要么1
    // print(Input.GetAxisRaw("Horizontal"));
    
    // 1 
    print(Input.GetAxis("Fire1"));

  }
}

Edit -> Project Settings -> Input Manager 有一些输入的自定义设置,
在这里插入图片描述
在这里插入图片描述

InputSystem(新输入系统)

2019年推出的系统,设备和动作分离,比老板复杂,分为Input Action、input signal bindings、Devices。
在这里插入图片描述
这里设置为新输入系统。
在assets目录新建settings目录,再新建 Input Actions
在这里插入图片描述
点击 edit asset
在这里插入图片描述
创建一个移动的action,配置 action type是value,control type是 vector2,并添加WASD的键盘绑定。
在这里插入图片描述

创建 control schema
在这里插入图片描述
并在这里勾选
在这里插入图片描述
也可以让unity自动生成配置,在add component的列表中
在这里插入图片描述
这里Behavior可以选择 Invoke Unity Events,可以绑定脚本事件
在这里插入图片描述
也可以输入代码方式,调用 PlayerInput
在这里插入图片描述

using UnityEngine;
using UnityEngine.InputSystem;

public class InputDemo : MonoBehaviour {
    
    
  
  // PlayerControl 是自动生成的脚本的名字
  public PlayerControl inputControl;

  public Vector2 dir;
  
  void Awake() {
    
    
    inputControl = new PlayerControl();
  }

  private void OnEnable() {
    
    
    inputControl.Enable();
  }

  private void OnDisable() {
    
    
    inputControl.Disable();
  }

  void Update() {
    
    
    dir = inputControl.Player.Move.ReadValue<Vector2>();
    print(dir.x);
  }


  public void Move1() {
    
    
    print("moving....");
  }
}

时间

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

// Time.deltaTime -> Time.unscaledDeltaTime 
// Time.time -> Time.unscaledTime === Time.realtimeSinceStartup (游戏真实运行时间)
public class TimeDemo : MonoBehaviour
{
    
    

    // Update不受timeScale影响,但是加上了Time.deltaTime就被影响了
    // unscaledDeltaTime不受缩放影响的每帧间隔
    private void Update()
    {
    
    
       // this.transform.Rotate(0, 100 * Time.deltaTime, 0);
       // this.transform.Rotate(0, 100 * Time.unscaledDeltaTime, 0);
    }


    private void FixedUpdate() {
    
    
        // this.transform.Rotate(0, 100, 1);
    }

    private void OnGUI()
    {
    
    
        if (GUILayout.Button("Time"))
        {
    
    
            // 游戏开始的秒数
            print(Time.time);

            // 每帧的间隔时间  0.004
            print(Time.deltaTime);

            // 可以保证在机器性能不一样的情况下,保存转速恒定
            // 机器好的情况下,渲染速度快 Time.deltaTime小,反之,渲染速度慢,Time.deltaTime大
            this.transform.Rotate(0, 100 * Time.deltaTime, 1);

        }

        if (GUILayout.Button("暂停游戏"))
        {
    
    
            Time.timeScale = 0;
        }
        if (GUILayout.Button("继续游戏"))
        {
    
    
            Time.timeScale = 1;
        }
    }
}


资源加载

资源要放到Assets/Resources目录, 使用 Resources.Load 方法加载,如果是加载FBX模型是GameObject对象,但是不会再Hierachy面板出现,需要Instantiate复制一个实例才能显示出来。

public class ResourcesLoad : MonoBehaviour
{
    
    
    // 按下w播放音乐
    void Update()
    {
    
    
        if (Input.GetKeyDown(KeyCode.W))
        {
    
    
            // ,不需要写扩展名
            var music = Resources.Load("Audios/bg");
            AudioSource.PlayClipAtPoint(music as AudioClip, Camera.main.transform.position);
        }
    }
}

场景切换

新建两个场景 Scene1 和 Scene2,拖拽到BuildSetting面板中,注意,这个顺序就显示的顺序
在这里插入图片描述

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

public class ToggleScene : MonoBehaviour
{
    
    
    public void Scene1(){
    
    
		// 新版切换场景方法
        SceneManager.LoadScene("Scene1");
        // 老版切换场景方法,已废弃
        Application.LoadLevel("Scene1");
    }

    public void Scene2(){
    
    
        SceneManager.LoadScene("Scene2");
    }

}

音频

一般都是 MP3和WAV文件,AudioSource播放组件,AudioListener收听音乐,摄像机默认都有一个AudioListener
在Camera新增一个AudioSource,并添加音乐到 AudioClip中。
在这里插入图片描述


public class AudioControl : MonoBehaviour
{
    
    

    private AudioSource a;

    void Start()
    {
    
    
        this.a = GetComponent<AudioSource>();
    }

    void Update()
    {
    
    
        if (Input.GetKeyDown(KeyCode.Q))
        {
    
    
            // this.a.Play();
            // 剪辑,位置,音量
            AudioSource.PlayClipAtPoint(this.a.clip, Camera.main.transform.position, 0.1f);
        }
    }
}

动画 Animation

  • unity的老版本只有Animation组件,在4.6版本以后则增添了Animator。
  • 只是控制一个动画的播放则用Animaton组件,如果是很多动画之间相互转换则使用Animator组件,两者的区别就是Animator有一个动画控制器(俗称动画状态机),使用它来进行动画切换是非常方便的,但缺点是占用内存比Animaton组件大。
  • 动画结束后,可以添加事件(挂载的游戏对象的脚本),可以通过这个特性,使得很多动画循环播放

动画的使用方式如下。

  1. 新建一个游戏物体
  2. 在游戏物体添加Animation组件
    在这里插入图片描述
  3. 打开Animation面板 window - Animation
  4. 选中游戏对象,在游戏面板中,点击add property,0:10代表1秒10帧(60帧为1秒)。
    在这里插入图片描述
    点击红点,红点背景是红色而且右边有红条,代表录制中
    在这里插入图片描述

也可以点击curves,直接操作曲线
在这里插入图片描述

选中右侧的点,录制开始和结束状态,就可以播放动画了,左键+alt可以拖动动画面板

在这里插入图片描述
录制好之后,在inspector面板 Animation组件的Animation属性,选中这个动画就可以播放了。
在这里插入图片描述
新建脚本


public class DoorControl : MonoBehaviour{
    
    

    public bool open = false;

    public Animation animation1;

    public string animationName = "Door";

    void Start(){
    
    
      this.animation1 = this.GetComponent<Animation>();
    }

    void OnMouseDown(){
    
    
      if(!this.animation1.isPlaying){
    
    
        if(open){
    
    
            // 倒着播放
            this.animation1[animationName].speed = -1;
            // 没有这句话,就是 0 - 0了,门瞬间关闭了,从最后开始播放
            this.animation1[animationName].time =this.animation1[animationName].length;
         }else {
    
    
            this.animation1[animationName].speed = 1;
         }
         this.animation1.Play(animationName);
         open = !open;
      }
    }
}

动画播放模式

loop就是循环播放,once一次,pingpang就是来回回来播放,clapforver固定播放到最后一帧(一直播放中)
在这里插入图片描述

动画 Animator

动画机含有多个动画的片段,并使用controller通过parameter控制。可以在Project面板右键Animator Controller来创建。
右键 Make transition可以连到下一个动画,点击线可以添加切换的条件,连线可以选中点击delete删除,条件的值可以在脚本设置。

  • 选中游戏对象,添加组件,添加Animation,会自动创建 Animator的
  • 选中游戏对象,菜单window,Animation,会自动打开,这个游戏对象上的动画
    在这里插入图片描述

   

PlayerPrefs本地存储

类似浏览器的LocalStorage,但是不存在文件系统,只有游戏卸载了才不存在。

public class PlayerPrefsTest : MonoBehaviour
{
    
    
    void Update()
    {
    
    
        if (Input.GetKeyDown(KeyCode.Z))
        {
    
    
            PlayerPrefs.SetInt("level", 11);
            PlayerPrefs.SetFloat("level1", 11f);
            PlayerPrefs.SetString("level11", "11");
        }
    }

    private void OnMouseDown()
    {
    
    
        print(PlayerPrefs.GetInt("level"));
    }
}


协同 Coroutine

当前代码,在等待某些资源条件好的时候,在未来执行。

public class CoRoutineTest : MonoBehaviour
{
    
    
    void Update()
    {
    
    
        if(Input.GetKeyDown(KeyCode.L)){
    
    
           StartCoroutine(play1());
        }
    }
	
	// 2秒后再print(2)
    IEnumerator play1(){
    
    
      print(1);
      yield return new WaitForSeconds(2);
      print(2);
    }
}


www类

基于HTTP的网络传输功能,需要结合协同使用。

public class WWWDemo : MonoBehaviour
{
    
    

    private Texture2D pic;

    void Start(){
    
    
        
    }

    void Update() {
    
    
      if(Input.GetKeyDown(KeyCode.O)){
    
    
         StartCoroutine(DrawPic());
      }
    }

    void OnGUI(){
    
    
        if(pic != null){
    
    
          GUI.DrawTexture(new Rect(0,0,100,100), pic);
        }
    }

    IEnumerator DrawPic(){
    
    
      WWW w3 = new WWW("https://img0.baidu.com/it/u=3111329697,2164934529&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=313");
      yield return w3;
      pic = w3.texture;
    }
}


UnityWebRequest

可以用WWW或者HttpWebRequest来实现文件的下载。因为WWW不存在设置timeout属性,因此当我们网络不好请求超时的时候,无法简单的做出判断。当网络极差的时候,游戏下载将会停止(即一直在等待yield return www)当时间较长时网络恢复将无法继续下载,也没有提示,需要重启才能重新下载。Unity早在5.4版本的时候就出了新的API UnityWebRequest用于替代WWW。

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

public class HttpTest : MonoBehaviour
{
    
    
  private string jsonUrl = "http://localhost:8888/demo.json";

  private void Start()
  {
    
    
    StartCoroutine(Get());
  }

  IEnumerator Get()
  {
    
    
    UnityWebRequest request = UnityWebRequest.Get(jsonUrl);
    yield return request.SendWebRequest();
    if(request.isHttpError || request.isNetworkError)
    {
    
    
      Debug.LogError(request.error);
    }
    else
    {
    
    
      string receiveContent = request.downloadHandler.text;
      Debug.Log(receiveContent);
    }
  }
}

人称设置

第一人称,把camera直接拖到物体内部并且position一致。
第三人称,把camera直接拖到物体内部而且位置不一样,可以看到自己。
如下,实现了前后左右移动的效果

public class CubeMove : MonoBehaviour
{
    
    

    private float speed;

    private float angleSpeed;

    void Start(){
    
    
        speed = 1f;
        angleSpeed = 5f;
    }

    void Update() {
    
    
        transform.Translate(Vector3.forward * speed * Time.deltaTime * Input.GetAxisRaw("Vertical"));
        transform.Rotate(Vector3.up, Input.GetAxisRaw("Horizontal") * angleSpeed * Time.deltaTime );
    }
}

Streaming Assets

Unity 中的大多数资源在构建时都会合并到项目中。但是,将文件放入目标计算机上的普通文件系统以使其可通过路径名访问有时会很有用。

物理引擎

模拟真实事件的物体碰撞,跌落,使用了 nvida 的physX。新建cube,add component,选择 Physics给cube添加一个刚体,
Rigidbody刚体表示受力的作用,Collider表示使用碰撞检测。

using UnityEngine;

namespace Scene4 {
    
    
  public class RigidControl : MonoBehaviour {
    
    


    private Rigidbody obj;
    
    private void Start() {
    
    
      obj = GetComponent<Rigidbody>();
    }
    
    private void Update() {
    
    
      if (Input.GetKeyDown(KeyCode.A)) {
    
    
        // 给物体添加一个右上方的力, Impulse是冲击力,瞬间爆发
        obj.AddForce(new Vector3(1, 1, 0) * 10, ForceMode.Impulse);
      }
      
      if (Input.GetKeyDown(KeyCode.Q)) {
    
    
        // 给物体添加一个右上方的力, 牵引力,不停的按下才可以
        obj.AddForce(new Vector3(1, 1, 0) * 100, ForceMode.Acceleration);
      }

      if (Input.GetKeyDown(KeyCode.W)) {
    
    
        // 直接给物体一个速度
        obj.velocity = new Vector3(1, 1, 0) * 3;
      }
    }
    
  }
}


碰撞检测

地板Plane都有默认的 Mesh Collider,发生碰撞的条件是两个物体之间都有Collider并且至少一方有刚体。Box Collider也有材质但是和MeshRenderer的材质(渲染材质)不一样,这个叫做物理材质(不同的物理属性)。可以使用右键 Physics Material 创建物理材质。

在这里插入图片描述
面板中的属性依次是动摩擦系数、静摩擦系数、弹性(物体落下弹起的效果)、摩擦系数合并(比如取最大值)

在这里插入图片描述
可以调整碰撞体的大小(不接触物体但是能碰到的效果)。

is Trigger 勾选了就是trigger触发器,没有力的作用(比如小球和地面,勾选了直接就穿过了地面)。

碰撞有2种消息(trigger的和非trigger的),3种状态(Enter、Stay、Exit)。


// 被碰到的物体的名字
private void OnCollisionEnter(Collision c) {
    
    
  print("OnCollisionEnter" + c.gameObject.name);
}

private void OnCollisionStay() {
    
    
  print("OnCollisionStay");
}

private void OnCollisionExit() {
    
    
  print("OnCollisionExit");
}

private void OnTriggerEnter() {
    
    
  print("OnTriggerEnter");
}
    

射线

射线是一个点向另一个点发射一条线,一旦和其他的模型发生碰撞,就停止发射。射线是摄像机发出的。
鼠标点击一个点,摄像机和这个点连线,之后和哪个物体发生了碰撞就是点击了哪个物体。

using UnityEngine;

public class RayTest : MonoBehaviour {
    
    
    
    // 游戏对象 添加组件 LineRenderer
    private LineRenderer line;

    private void Start() {
    
    
        line = GetComponent<LineRenderer>();
    }

    private void Update() {
    
    
        if (Input.GetMouseButtonDown(0)) {
    
    
            if (Camera.main) {
    
    
                print(Input.mousePosition);
                Ray r = Camera.main.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                // 如果和某物体发生了碰撞, 100内,第4个参数是layer,默认所有层,这个层是二进制 1 << 8 代表第8层
                if (Physics.Raycast(r, out hit, 100f)) {
    
    
                    // 碰撞的点
                    print(hit.point);
                    // 碰到的物体
                    print(hit.collider.gameObject);
                    line.enabled = true;
                    line.SetPosition(1, hit.point);
                    // 销毁碰到的物体
                    Destroy(hit.collider.gameObject);
                }
                else {
    
    
                    line.enabled = false;
                    print("没碰撞");
                }
            }
        }
    }
}



角色控制器

主要应用到会动的游戏物体上,不会动的就用刚体就好了,也不需要Collider来碰撞检测了。
在这里插入图片描述
前两项是坡度(超过这个度数爬不了)和最小位移,角色控制器默认给加胶囊体。


using UnityEngine;

public class MoveMove : MonoBehaviour {
    
    

    private CharacterController controller;

    private Camera main;

    private float speed;
    void Start() {
    
    
        controller = GetComponent<CharacterController>();
        speed = 10000f;
        main = Camera.main;
    }

    void Update() {
    
    
        // SimpleMove 有重力的作用,move没有
        // controller.SimpleMove()
   
        // 这个是世界坐标系
        // controller.SimpleMove(Vector3.forward * speed *  Time.deltaTime * Input.GetAxisRaw("Horizontal"));
        
        // 推荐使用本地坐标系,往自己的前方走
        controller.SimpleMove(transform.forward * speed *  Time.deltaTime * Input.GetAxisRaw("Horizontal"));
    }

    private void LateUpdate() {
    
    
        // 摄像机跟随
        // main.transform.position = transform.position + new Vector3(0, 0, -1f);
    }
}

Layer层

在这里插入图片描述
在这里插入图片描述
最多32个层,内置0-7改不了,是二进制表示 1 << 8代表第8层。

地形

GameObject Terrain,可以创建一些自定义的山啊什么的,一般不使用,一般使用自定义地形。

Gizmos 辅助线框类

在这里插入图片描述
点击可以切换是否显示,点箭头可以展开更多选项
在这里插入图片描述

导航 navigation

导航就是任务行走路线,也可以自动避开一些障碍物。菜单windows -> AI -> navigation打开面板。
在这里插入图片描述
创建一个这样的地形,选中所有的物体,然后
在这里插入图片描述

在这里插入图片描述
给运动的物体,添加一个导航组件
在这里插入图片描述
给物体绑定脚本

using UnityEngine;
using UnityEngine.AI;

public class NavController : MonoBehaviour {
    
    

    private NavMeshAgent a;
    
    void Start() {
    
    
      a = GetComponent<NavMeshAgent>();
    }

    void Update() {
    
    
      if (Input.GetMouseButtonDown(0)) {
    
    
        var r = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;
        if (Physics.Raycast(r, out hit)) {
    
    
          var p = hit.point;
          a.SetDestination(p);
          
        }
      }
    }
}


动态障碍物

给中间的门去掉 static navigation,并且添加 Nav Mesh Obstacle组件
在这里插入图片描述

网格链接(就是导航区域可链接起来)
比如可以从高台跳下去
首先,选中可以跳的物体勾选上
在这里插入图片描述
然后设置高度(下边选项是跳跃距离),并重新烘焙即可
在这里插入图片描述

如果有两个特定地方,需要链接,首先在场景创建两个地点cube。然后添加 Off Mesh Link 组件

在这里插入图片描述

拓展右键菜单

编辑器使用的代码应该仅限于编辑模式下,也就是说正式的游戏包不应该包含这些代码。Unity提供了一个规则:如果属于编辑模式下的代码,需要放在Editor文件夹下;如果属于运行时执行的代码,放在任意非Editor文件夹下即可。
在这里插入图片描述
如图,在editor目录下新建脚本。

using UnityEngine;
using UnityEditor;

public class TestMenu : MonoBehaviour
{
    
    
    [MenuItem("Assets/My Tools/Tools 1",false,2)]
    static void MyTools1()
    {
    
    
        Debug.Log(Selection.activeObject.name);
    }
    
    [MenuItem("Assets/My Tools/Tools 2",false,1)]
    static void MyTools2()
    {
    
    
        Debug.Log(Selection.activeObject.name);
    }
}

效果如下

在这里插入图片描述

练习 demo

物体朝着另一个物体移动,到达就停止

using UnityEngine;

public class Move1 : MonoBehaviour {
    
    

    private GameObject target;
    
    // 选中物体,Edit-> lock view to selected就可以运行游戏时,随着物体的视角
    private void Start() {
    
    
        // 建议unity一个固定的帧率,不一定有效
        Application.targetFrameRate = 60;
        target = GameObject.Find("Cube1");
        transform.LookAt(target.transform);
        print($"初始距离是{
      
      getDistance()}");
    }

    float getDistance() {
    
    
      return (transform.position - target.transform.position).magnitude;
    }


    void Update() {
    
    
      if (getDistance() < 0.1f) {
    
    
        print("已经到达目标");
      }else {
    
    
        print("moving");
        this.transform.Translate(0,0, 1.2f * Time.deltaTime);
      }
    }
}

循环创建几个Cube

int a = 0;

while(a < -1){
    
    
   //我们将obj1初始化为一个Cube立方体,当然我们也可以初始化为其他的形状
   GameObject obj1 = GameObject.CreatePrimitive(PrimitiveType.Cube);
   //设置物体的位置Vector3三个参数分别代表x,y,z的坐标数
   obj1.transform.position = new Vector3((float)(1 +  a * 1.3),1,1);
   //给这个创建出来的对象起个名字
   obj1.name = ("dujia" + a);
   a++;
}

获取其他游戏对象脚本变量的值

  
GameObject gb = GameObject.FindWithTag("Enemy");
print(gb.name);
print(gb.GetComponent<Enemy>().money);

查找所有敌人的血量

Enemy .cs

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

public class Enemy : MonoBehaviour
{
    
    
    public int money = 100;
}


新建两个 Enemy0和Enemy1的游戏对象,并绑定上述脚本

public class GameObject1 : MonoBehaviour
{
    
    
    void OnGUI()
    {
    
    
        if (GUILayout.Button("GameObject1"))
        {
    
    

            // 单个敌人
            GameObject gb = GameObject.FindWithTag("Enemy");
            print(gb.name)
            print(gb.GetComponent<Enemy>().money);

            // 如果有多个
            GameObject[] gbs = GameObject.FindGameObjectsWithTag("Enemy");
            for (int i = 0; i < gbs.Length; i++)
            {
    
    
                print(gbs[i].GetComponent<Enemy>().money);
            }

            Enemy[] all = Object.FindObjectsOfType<Enemy>();
            for (int i = 0; i < all.Length; i++)
            {
    
    
               if(all[i].money < 60){
    
    
                  all[i].GetComponent<MeshRenderer>().material.color = Color.red;
               }
            }

        }
    }
}

层级未知查找子物体

public class TransFormHelper
{
    
    
    // 层级未知查找子物体
    public static Transform Getchild(Transform parent, string kidName)
    {
    
    
        Transform childTransForm = parent.Find(kidName);
        if (childTransForm != null)
        {
    
    
            return childTransForm;
        }
        int count = parent.childCount;
        for (int i = 0; i < count; i++)
        {
    
    
            Transform ct = Getchild(parent.GetChild(i), kidName);
              if (ct != null)
            {
    
    
                return ct;
            }

        }
        return null;
    }

}

// 使用
Transform tf = TransFormHelper.Getchild(this.transform, "trans1");
tf.GetComponent<MeshRenderer>().material.color = Color.red;

// 判断两个物体的距离
print(Vector3.Distance(this.transform.position, tf.position));

实现倒计时

在Hierachy面板,右键UI选择Text,新建文本。注意,TextMeshPro 是 Unity 的最终文本解决方案。它是 Unity UI Text 和旧版 Text Mesh 的完美替代方案。

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

public class Counter : MonoBehaviour{
    
    

  public int second = 120;

  private float nextTime = 1;

  private float totalTime = 0;

  private TMPro.TextMeshProUGUI text1;

  void Start(){
    
    
    this.text1 = this.GetComponent<TMPro.TextMeshProUGUI>();
    // 从1s开始重复调用,间隔也是1s
    InvokeRepeating("TimerFn3", 1, 1);

    // 3s后执行
    // Invoke("TimerFn3", 3);
  }


  void TimerFn3() {
    
    
    if (second > 0){
    
    
        second--;
        var res = string.Format("{0:d2}:{1:d2}", second / 60, second % 60);
        this.text1.text = res;
        if (second < 10){
    
    
          this.text1.color = Color.red;
        }
      }else {
    
    
        CancelInvoke("TimerFn3")
      }
  }

	// 适合发射子弹
  void TimerFn() {
    
    
    if (Time.time > nextTime) {
    
    
      if (second > 0){
    
    
        second--;
        var res = string.Format("{0:d2}:{1:d2}", second / 60, second % 60);
        this.text1.text = res;
        nextTime = Time.time + 1;
        if (second < 10){
    
    
          this.text1.color = Color.red;
        }
      }
    }
  }

  void TimerFn2(){
    
    
    totalTime += Time.deltaTime;
    if (totalTime >= 1) {
    
    
      totalTime = 0;
      if (second > 0){
    
    
        second--;
        var res = string.Format("{0:d2}:{1:d2}", second / 60, second % 60);
        this.text1.text = res;
        if (second < 10){
    
    
          this.text1.color = Color.red;
        }
      }
    }
  }

  void Update(){
    
    
    // this.TimerFn2();
  }
}

打箱子游戏

新建9个箱子,1个子弹,都做成预制体

新建一个空的游戏对象,加下面的脚本

using UnityEngine;

public class HitBox : MonoBehaviour {
    
    

    // 定义为public,可以在软件中看到,并且可以直接预制体拖过去就可以
    public GameObject Box;
    
    public GameObject Bullet;

    public Texture2D pointer;

    private float FireTime1, FireTime2;
    
    private void Start() {
    
    
        for (int i = 0; i < 5; i++) {
    
    
            for (int j = 0; j < 5; j++) {
    
    
                var box = Instantiate(Box);
                box.transform.position = new Vector3(-2+i, 0.5f+j, 4.5f);
            }
        }

        FireTime1 = 0.5f;
        FireTime2 = 0;
        Cursor.SetCursor(pointer, new Vector2(pointer.width / 2,pointer.height / 2), CursorMode.Auto);
    }
    
    private void Update() {
    
    
        if (Input.GetButton("Fire1") && Camera.main) {
    
    
            FireTime2 += Time.deltaTime;
            if (FireTime2 >= FireTime1) {
    
    
                FireTime2 = 0;
                var bullet = Instantiate(Bullet);
                bullet.transform.position = Camera.main.transform.position;
                var r = Camera.main.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                // 这样会出来很多小球
                if (Physics.Raycast(r, out hit, 50)) {
    
    
                    bullet.GetComponent<Rigidbody>().velocity = (hit.point - bullet.transform.position) * 10;
                }
                else {
    
    
                    print("没碰撞");
                }
            }
        }
    }
}


当子弹和箱子不可见时,需要销毁,所以给它们附加如下的脚本

using UnityEngine;

public class DistroyOBj : MonoBehaviour
{
    
    
    private void OnBecameInvisible() {
    
    
        Destroy(this.gameObject);
    }
}

可以设置摄像机的远方距离,做一下优化
在这里插入图片描述
下载一个瞄准的图片素材,变成cursor
在这里插入图片描述

参考资料

猜你喜欢

转载自blog.csdn.net/qq_29334605/article/details/132762389