unityP1笔记——Unity部分

目录

Unity基本操作

组件思想

Transform组件

GameObject组件

预制体

生命周期函数

 Invoke函数

协程

常用工具类

Unity2D

Sprite和SpriteRender

2D物理系统:刚体

碰撞体

触发Trigger(基于碰撞体)

输入系统

键盘输入

鼠标检测

InputManager​编辑

小球案例-移动/跳跃

UI系统

Text组件

Button组件

InputField组件

Toggle组件

案例:注册与登录

Slider组件

ScrollBar组件

ScrollView 滚动视图组件

遮罩组件Mask  

ScrollView

GridLayoutGroup 表格布局组件

UI布局

Canvas画布

UI事件

Unity3D

刚体、碰撞体、触发、物理材质(与2D类似)

物理射线

刚体移动

案例-方块保卫战

摄像机Camera 

Shader概念

粒子系统

动画系统

模型资源与动画设置

人型动画的复用性

动画事件

角色控制器Character Controller

导航系统

​编辑NavMeshObstacle障碍物组件

案例:鼠标右击移动

 Unity资源管理

预制体实例化

Resource资源加载

数据存档

游戏场景加载

声音系统

AudioListener组件

 AudioSource组件

案例:玩家设置保存音量

 打包发布


Unity基本操作

界面

Scene

  场景漫游

 

组件思想

自定义组件

 Transform组件

void Update()
    {
        transform.position = new Vector3(10, 10, 10);  //构造新位置
    }

移动

    private float num = 0;
    void Update()
    {
        transform.position = new Vector3(num, 10, 10);
        num += 0.01f;
    }

 常用属性&方法:

 *Rotation并不代表Inspector窗口里对应的xyz值,Rotation含有四个属性。如果想通过数值直接改变xyz,则要通过欧拉角eulerAngles改变。

GameObject组件

GameObject是一个类型

 gameObject是一个属性

        private GameObject go;
        public Transform ts;

        //属性
        print(gameObject.name);  //Cube
        print(gameObject.tag);  //Cube
        print(gameObject.activeInHierarchy);  //True
        print(go.activeInHierarchy);  //False
        private GameObject go;
        //方法
        //Find()
        go = GameObject.Find("C");
        //GameObject.Find("C/D");  //查找C下的子物体D(从根目录进行查找)
        print(go.transform.position);

        //GetComponent<T>()
        Transform tempTransform = gameObject.GetComponent<Transform>();
        print(tempTransform.position);
        BoxCollider boxCollider = gameObject.GetComponent<BoxCollider>();
        print(boxCollider.isTrigger);

        //SetActive()
        gameObject.SetActive(false);

*注意:这里的GetComponent<T>()里的T不可以是GameObject,因为严格来说GameObject并不算是一个组件。

预制体

如果丢失某件预制体(删除了Prefab),可以进行如下操作:

*注意:预制体的操作无法Ctrl+Z撤销,谨慎操作!

 

 此时再改动原始预制体(即上图圆球),并不会应用到已经嵌套在其他预制体里的预制体变体。

如果命名一个小兵为小兵A,然后将其拖拽成预制体小兵B,若选择【Original Prefab】,并拖拽一个小兵B进场景

 

 此时如果在预制体异世界里更改小兵B,将帽子改为红色,则场景中出现:

 现在再把小兵B拖拽预制体成为小兵C,选择【Prefab Variant】,此时层级面板里小兵B的图标会发生变化,由于小兵B由小兵A变化而来,改动小兵A使得身体变成红色,则场景中:

但是如果更改小兵B(即改动预制体小兵C)如图

 

则下方的小兵A(预制体小兵B)不会发生变化

 预制体A(小兵A)是原始的,预制体B(小兵A)是预制体A制造出来的原始预制体(独立的),预制体C(小兵B)是预制体B制造的预制体变体

 生命周期函数

常用生命周期函数

    private void Awake()  //只激活一次
    {
        print("Awake");
    }
    private void OnEnable()  //每次启动都会激活
    {
        print("OnEnable");
    }
    void Start()  //与Awake唯一区别是Start后执行
    {
        print("Start");
    }
    void Update()  //每一帧都执行
    {
        print("Update");
        print(Time.deltaTime);  //渲染上一帧的时间
    }
    private void LateUpdate()  //在Update后执行
    {
        print("LateUpdate");
    }
    private void FixedUpdate()  //固定物理时间每0.02s执行一次
    {
        print("FixedUpdate");
    }
    private void OnDisable()  //每次禁用执行
    {
        print("OnDisable");
    }
    private void OnDestroy()  //组件被删除执行,先OnDisable再OnDestroy
    {
        print("OnDestroy");
    }

 Invoke函数

    void Start()
    {
        //Invoke("Demo", 3);
        InvokeRepeating("Demo", 3, 1);  //3秒后,每过1秒执行一次Demo()
        Invoke("CancelDemo", 5);
    }

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

    private void CancelDemo()
    {
        CancelInvoke("Demo");
    }

 以上Demo()会执行2次

 协程

协程定义方式:

 

执行协程函数:

    void Start()
    {
        //StartCoroutine("Demo");
        StartCoroutine(Demo(10));  //这种方式更好,因为可以传参数
    }

    public IEnumerator Demo(int num)
    {
        print("先执行的,传参为" + num);
        //等待1秒
        yield return new WaitForSeconds(1.0f);
        print("后执行的");
        //下一帧执行
        yield return null;
        print("最后执行的");
    }

 

    public IEnumerator Demo2()
    {
        while (true)  //协程函数可以这么执行,会一直循环
        {
            //等待0.1秒
            yield return new WaitForSeconds(0.1f);
            transform.Rotate(new Vector3(5, 0, 0));
        }
    }
    public IEnumerator Demo3()
    {
        //Demo3()的逻辑是:先位移再进入Demo2()开始旋转
        transform.position = new Vector3(10, 10, 10);
        yield return Demo2();  //可以进入到另一个协程
    }

停止协程:

    public IEnumerator Demo4()
    {
        //5秒之后取消协程
        yield return new WaitForSeconds(5);

        StopAllCoroutines();  //取消全部协程
        StopCoroutine("Demo");
    }

 或者

    Coroutine cor = StartCoroutine("Demo");
    StopCoroutine(cor);

常用工具类

数学工具类

    void Start()
    {
        int num1 = Mathf.Abs(-6);
        int num2 = Mathf.Max(2,4,10,50,6);
        int num3 = Mathf.Min(2,4,10,50,6);
        float num4 = Mathf.Round(2.5f);  //四舍六入,五取偶数
        float num5 = Mathf.Ceil(2.4f);  //向上取整
        float num6 = Mathf.Floor(2.6f);  //向下取整

        print(num1);
        print(num2);
        print(num3);
        print(num4);
        print(num5);
        print(num6);
    }

        //返回 [0,5)
        int num1 = Random.Range(0, 5);
        print(num1);
        //返回[0, 5]
        float num2 = Random.Range(0, 5.0f);
        print(num2);

时间工具类

        void Update()
        {
        //一秒上升一米 方向 * 速度 * 一帧花费时间s
        transform.Translate(new Vector3(0, 1f, 0) * 1 * Time.deltaTime);
        }
    void Start()
    {
        Time.timeScale = 0;
    }

    void Update()
    {
        print("游戏时间为:" + Time.time);  //只打印一次为0
        print("现实时间为:" + Time.realtimeSinceStartup);
    }

Unity2D

Sprite和SpriteRender

照相机模式更改 Orthographic-正交模式(无近大远小),Perspective-透视模式

 

OrderinLayer是同层的排序值,值越小越靠后

将Sprite替换为另外的Sprite(由于还没涉及到资源管理,此处为public拖拽使用)

    private SpriteRenderer spriteRenderer;
    public Sprite sprite;

    void Start()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
        spriteRenderer.sprite = sprite;
    }

2D物理系统:刚体

 *Kinematic可以用在主角身上,因为不希望主角除了代码以外还能被其他物体撞到产生移动(Kinematic的物体可以撞动别的Dynamic物体,但是别的物体撞它,它不会移动)

*Static可以用在建筑物等身上,因为不希望任何物体可以撞动它

*Simulated可以看作是刚体系统的启用与否

*Linear Drag 相当于空气阻力

Collision Detection:

物理材质

 

    private Rigidbody2D rd2D;
    void Start()
    {
        rd2D = GetComponent<Rigidbody2D>();
        rd2D.mass = 3;
        rd2D.gravityScale = 0.5f;
    }

    void Update()
    {
        //刚体移动的方法:
        // ①移动到某个坐标
        rd2D.MovePosition(transform.position + new Vector3(0, 1, 0) * Time.deltaTime);
        // ②强行让y轴受力改变为1(自由落体为负数)
        rd2D.velocity = new Vector2(0, 1);
        // ③施加力
        rd2D.AddForce(Vector2.up * 10);

        //瞬间停止移动:
        rd2D.velocity = Vector2.zero;
    }

 碰撞体

重点 

    private void OnCollisionEnter2D(Collision2D collision)
    {
        print("进入碰撞");
    }
    private void OnCollisionExit2D(Collision2D collision)
    {
        print("退出碰撞");
    }
    private void OnCollisionStay2D(Collision2D collision)
    {
        print("碰撞中");
    }

注意!!!

1.双方都没有碰撞体和刚体,绝对不可能产生碰撞事件/函数

2.双方都有碰撞体,可以碰撞

3.一方有刚体+碰撞体,另一方只有碰撞体,则两方都可以进入碰撞事件

4.双方都只有刚体,无法进入

触发Trigger(基于碰撞体)

注意与碰撞中相同

输入系统

键盘输入

    void Update()
    {
        // 只有第一次按下的一帧有效
        // 这里也可以不通过枚举而是字符串来识别,比如 Input.GetKeyDown("A")
        if (Input.GetKeyDown(KeyCode.A))
        {
            print("按下A");
        }
        // 只有第一次按下的一帧有效
        if (Input.GetKey(KeyCode.A))
        {
            print("持续按A");
        }
        // 持续有效
        if (Input.GetKeyUp(KeyCode.A))
        {
            print("弹起A");
        }
    }
        // ------模拟蓄力------
        if (Input.GetKeyDown(AttackKeyName))
        {
            print("开始蓄力");
            attackValue = 0;
        }
        if (Input.GetKey(AttackKeyName))
        {
            print("蓄力中");
            attackValue += Time.deltaTime;
        }
        if (Input.GetKeyUp(AttackKeyName))
        {
            print("发动攻击,攻击力为:" + attackValue);
        }

鼠标检测

        if (Input.GetMouseButtonDown(0))
        {
            print("按下左键");
        }
        if (Input.GetMouseButton(0))
        {
            print("持续按左键");
        }
        if (Input.GetMouseButtonUp(0))
        {
            print("抬起左键");
        }
        //鼠标位置
        //并不是游戏中的位置
        if (Input.GetMouseButtonDown(0))
        {
            print(Input.mousePosition);
        }

InputManager

 (掌握黄圈部分即可)

 区别:

小球案例-移动/跳跃

    private Rigidbody2D rdb2D;

    public float speed = 10;
    public float JumpPower = 500;

    private bool isOnGround = true;

    void Start()
    {
        rdb2D = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        Move();
        Jump();
    }

    /// <summary>
    /// 移动
    /// </summary>
    void Move()
    {
        float x = Input.GetAxis("Horizontal");
        //float y = Input.GetAxis("Vertical");
        Vector3 dir = new Vector3(x, 0, 0);
        transform.Translate(dir * Time.deltaTime * speed);
    }

    /// <summary>
    ///  跳跃
    /// </summary>
    void Jump()
    {
        if (Input.GetKeyDown(KeyCode.Space) && isOnGround)
        {
            rdb2D.AddForce(Vector2.up * JumpPower);
        }
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if(collision.gameObject.tag == "Ground")
        {
            isOnGround = true;
        }
    }

    private void OnCollisionExit2D(Collision2D collision)
    {
        if (collision.gameObject.tag == "Ground")
        {
            isOnGround = false;
        }
    }

UI系统

 Image组件

图片资源的相关属性:

//UI相关操作必须引入UI命名空间
using UnityEngine.UI;

 Text组件

对齐属性

 Button组件

 

 第一种:

 第二种:代码内添加监听

//头文件添加UI
using UnityEngine.UI;


    private Button button;
    private void Start()
    {
        button = GetComponent<Button>();
        //使用委托监听 AddListener 注意括号里只写方法名,不加括号
        button.onClick.AddListener(ButtonClick2);
    }
    
    public void ButtonClick()
    {
        //第一种方式
        print("Button Click");
    }
    public void ButtonClick2()
    {
        //第二种方式
        print("Button Click 2");
    }

 InputField组件

 比较重要:如何设置为密码形式 Standard -> Password

    private InputField inputField;
    void Start()
    {
        inputField = GetComponent<InputField>();
        inputField.text = "666";

 OnValueChanged 和 OnEndEdit 的区别:

    private InputField inputField;
    void Start()
    {
        inputField = GetComponent<InputField>();
        //用户输入的文本
        inputField.onValueChanged.AddListener(OnValueChanged);
        //用户退出输入时的文本
        inputField.onEndEdit.AddListener(OnEndEdit);
    }

    void OnValueChanged(string value)
    {
        print("OnValueChanged: " + value);
    }

    void OnEndEdit(string value)
    {
        print("OnEndEdit: " + value);
    }

 Toggle组件

举例:灯光开关

 

 

 Toggle Group:分组之后只能勾选一个

 案例:注册与登录

因为面板都是唯一的且都会使用,因此使用单例模式

 

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

/// <summary>
/// 主面板
/// </summary>
public class L5_7MainPanel : MonoBehaviour
{
    //单例模式
    public static L5_7MainPanel Instance;

    private Button registerBtn;
    private Button loginBtn;

    private void Awake()
    {
        Instance = this;
    }
    void Start()
    {
        registerBtn = transform.Find("RegisterButton").GetComponent<Button>();
        loginBtn = transform.Find("LogInButton").GetComponent<Button>();
        
        //监听事件
        registerBtn.onClick.AddListener(RegisterBtnClick);
        loginBtn.onClick.AddListener(LoginBtnClick);
    }

    void RegisterBtnClick()
    {
        //点击注册按钮 -> 打开注册面板,关闭主面板
        L5_7RegisterPanel.Instance.Show();
        gameObject.SetActive(false);
    }
    void LoginBtnClick()
    {
        //点击登录按钮 -> 打开登录册面板,关闭主面板
        L5_7LoginPanel.Instance.Show();
        gameObject.SetActive(false);
    }
    public void Show()
    {
        gameObject.SetActive(true);
    }
}

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

/// <summary>
/// 弹出浮窗
/// </summary>
public class L5_7FloatWindow : MonoBehaviour
{
    public static L5_7FloatWindow Instance;
    private Text infoText;
    private Button shutButton;

    private void Awake()
    {
        Instance = this;

        infoText = transform.Find("Info").GetComponent<Text>();
        shutButton = transform.Find("ShutButton").GetComponent<Button>();

        shutButton.onClick.AddListener(ShutButtonClick);

        gameObject.SetActive(false);
    }

    public void ShowInfo(string info)
    {
        gameObject.SetActive(true);
        infoText.text = info;
    }
    void ShutButtonClick()
    {
        //关闭按钮
        gameObject.SetActive(false);
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 管理/保存用户注册信息
/// </summary>
public class L5_7userInfo
{
    public string Username;
    public string Password;
    public bool Gender;

    //构造函数
    public L5_7userInfo(string username, string password, bool gender)
    {
        Username = username;
        Password = password;
        Gender = gender;
    }
}

public class L5_7GameManager
{
    private static L5_7GameManager instance;
    // List 保存注册后的用户信息
    public List<L5_7userInfo> UserInfos = new List<L5_7userInfo>();

    // 封装
    public static L5_7GameManager Instance
    {
        get
        {
            if (instance == null)  instance = new L5_7GameManager();
            return instance;
        }
    }

    // 保存用户信息
    public void SaveUserInfo(L5_7userInfo userInfo)
    {
        UserInfos.Add(userInfo);
    }
    // 获取用户信息
    public L5_7userInfo GetUserInfo(string userName)
    {
        // 通过用户名遍历查找用户
        for(int i=0; i < UserInfos.Count; i++)
        {
            if(userName == UserInfos[i].Username)
            {
                // 返回类型为 List<L5_7userInfo>
                return UserInfos[i];
            }
        }
        return null;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 注册面板
/// </summary>
public class L5_7RegisterPanel : MonoBehaviour
{
    public static L5_7RegisterPanel Instance;

    private InputField Username;
    private InputField Password;
    private InputField RePassword;
    private Toggle Gender; 
    private Button BackButton;
    private Button OKButton;

    private void Awake()
    {
        Instance = this;

        //注意!这里的路径没写对的话会报 空引用错误,注意检查
        Username = transform.Find("Username/InputField").GetComponent<InputField>();
        Password = transform.Find("Password/InputField").GetComponent<InputField>();
        RePassword = transform.Find("RePassword/InputField").GetComponent<InputField>();
        // 这里是为了获取 Gender下面的Male里的isOn(因为加了 ToggleGroup ,因此只需判断一个即可)
        Gender = transform.Find("Gender/Male").GetComponent<Toggle>();
        BackButton = transform.Find("BackButton").GetComponent<Button>();
        OKButton = transform.Find("OKButton").GetComponent<Button>();

        BackButton.onClick.AddListener(BackButtonClick);
        OKButton.onClick.AddListener(OKButtonClick);

        gameObject.SetActive(false);
    }

    void BackButtonClick()
    {
        //返回主面板
        L5_7MainPanel.Instance.Show();
        gameObject.SetActive(false);
    }
    void OKButtonClick()
    {
        // 确定注册
        // 判断是否全部填写
        if (string.IsNullOrEmpty(Username.text) || string.IsNullOrEmpty(Password.text) 
            || string.IsNullOrEmpty(RePassword.text))
        {
            L5_7FloatWindow.Instance.ShowInfo("请输入账号或密码!");
        }
        // 判断密码与重复密码是否一致
        else if(Password.text != RePassword.text)
        {
            L5_7FloatWindow.Instance.ShowInfo("密码与重复密码不一致!");
        }
        // 输入没有问题,查询判断是否已经注册过
        else
        {
            if(L5_7GameManager.Instance.GetUserInfo(Username.text) != null)
            {
                L5_7FloatWindow.Instance.ShowInfo("请勿重复注册!");
            }
            else
            {
                L5_7userInfo userInfo = new L5_7userInfo(Username.text, Password.text, Gender.isOn);
                //保存账户信息
                L5_7GameManager.Instance.SaveUserInfo(userInfo);
                L5_7FloatWindow.Instance.ShowInfo("注册成功,请登录!");
            }
        }
    }

    public void Show()
    {
        gameObject.SetActive(true);
        // 清空数据,防止下次打开时仍有上一次输入的数据
        Username.text = "";
        Password.text = "";
        RePassword.text = "";

        Gender.isOn = true;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 登录面板
/// 总体与注册面板几乎相同
/// </summary>
public class L5_7LoginPanel : MonoBehaviour
{
    public static L5_7LoginPanel Instance;

    private InputField Username;
    private InputField Password;
    private Button BackButton;
    private Button OKButton;

    private void Awake()
    {
        Instance = this;
        Username = transform.Find("Username/InputField").GetComponent<InputField>();
        Password = transform.Find("Password/InputField").GetComponent<InputField>();
        BackButton = transform.Find("BackButton").GetComponent<Button>();
        OKButton = transform.Find("OKButton").GetComponent<Button>();

        BackButton.onClick.AddListener(BackButtonClick);
        OKButton.onClick.AddListener(OKButtonClick);

        gameObject.SetActive(false);
    }

    void BackButtonClick()
    {
        //返回主面板
        L5_7MainPanel.Instance.Show();
        gameObject.SetActive(false);
    }

    void OKButtonClick()
    {
        //登录
        // 判断是否全部输入
        if (string.IsNullOrEmpty(Username.text) || string.IsNullOrEmpty(Password.text))
        {
            L5_7FloatWindow.Instance.ShowInfo("请输入账号或密码!");
        }
        // 判断用户是否在存储的用户数据里
        else
        {
            L5_7userInfo userInfo = L5_7GameManager.Instance.GetUserInfo(Username.text);
            if (userInfo == null)
            {
                L5_7FloatWindow.Instance.ShowInfo("用户不存在!");
            }
            // 比对密码是否正确
            else if(Password.text != userInfo.Password)
            {
                L5_7FloatWindow.Instance.ShowInfo("用户名或密码错误!");
            }
            else if (Password.text == userInfo.Password)
            {
                //核实
                L5_7FloatWindow.Instance.ShowInfo("登录成功!");
            }
        }
    }
    public void Show()
    {
        gameObject.SetActive(true);
        Username.text = "";
        Password.text = "";
    }
}

Slider组件

    private Slider slider;

    void Start()
    {
        slider = GetComponent<Slider>();
        slider.onValueChanged.AddListener(SliderOnValueChanged);
    }

    void SliderOnValueChanged(float value)
    {
        print(value);
    }

 ScrollBar组件

与Slider的区别:没有填充物(类似于控制浏览器放大缩小的滑动条)

 

 

ScrollView 滚动视图组件

遮罩组件Mask  

ScrollView

 

如图,超出部分会形成遮罩效果

 

 介绍三种移动方式(在此处选择)

① Unrestricted 拖拽移动后不会回到原本的位置

② Elastic 拖拽后会回弹,回弹速度及幅度由Elasticity 大小决定,越小越快

③ Clamped 类似于浏览器的滑动条,拖拽到某处不会变化(注意要把Content的大小调整到与实际内容大小相符,这样ScrollBar才会相应调整)

GridLayoutGroup 表格布局组件

 

 UI布局

 锚点就是指 ① 相对父物体进行怎样的定位

                    ② 相对父物体进行怎样的弹性拉伸

首先是这个区域(定位),即锚点在父物体的某个位置。此时的PosX和PosY值都是子物体的中心点对于父物体的锚点而言的相对位置。选择这个区域会锁定中心点和锚点间的相对定位

 然后是这个区域(弹性布局),这个区域是保持相对于父物体某方向上的距离。最右下角的即四角固定

 Canvas画布

UI事件

//头文件需补充
using UnityEngine.EventSystems;

//接口需继承
public class L5_15UIEventSystem : MonoBehaviour, IPointerClickHandler

//继承需实现
    public void OnPointerClick(PointerEventData eventData)
    {
        print("OnPointerClick");
    }

 

Unity3D

刚体、碰撞体、触发、物理材质(与2D类似)

Is Kinematic 选中指的是希望代码接管物体,如果需要仿真模拟不选即可

物理射线

    void Update()
    {
        // public Ray(Vector3 origin, Vector3 direction);
        Ray ray = new Ray(Vector3.zero, new Vector3(0,1,0));   //从0点发射的向上(0,1,0)的线
        if (Physics.Raycast(ray))
        {
            print("射线碰到了某个物体");
        }
    }
        // 重载:public static bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance);
        if (Physics.Raycast(ray, out RaycastHit hitInfo, 1000))
        {
            print(string.Format("射线在{0}处碰到了{1}", hitInfo.point, hitInfo.collider.name));
        }

RaycastHit 里 hitInfo 的属性:

Debug.DrawLine(ray.origin, hitInfo.point, Color.green);

        //需以屏幕中心为目标,如射击游戏
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

 刚体移动

    private Rigidbody rb;

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

    //固定以 0.02s 更新
    void FixedUpdate()
    {
        Move();
    }

    void Move()
    {
        //获取输入
        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");

        Vector3 offset = new Vector3(x, 0, z) * 0.02f * speed;

        rb.MovePosition(transform.position + offset);
    }

案例-方块保卫战

 

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

/// <summary>
/// 控制敌人
/// </summary>
public class L6_8Enemy : MonoBehaviour
{
    private Rigidbody rb;
    private float speed = 2.5f;
    private int HP = 100;

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

    void FixedUpdate()
    {
        Move();
    }

    void Move()
    {
        Vector3 offset = transform.forward * Time.fixedDeltaTime * speed;

        rb.MovePosition(transform.position + offset);
    }

    public void Hurt(int damage)
    {
        HP -= damage;
        if(HP <= 0)
        {
            //死亡
            Dead();
        }
    }

    private void Dead()
    {
        Destroy(gameObject);
        //检查是否还有同类,若没有则玩家游戏胜利
        L6_8Player.Instance.EnemyDead(this);
    }
}

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

/// <summary>
/// 控制玩家(这里实际上是从摄像机发送射线,设计一个空物体来检测是否有敌人进攻成功)
/// </summary>
public class L6_8Player : MonoBehaviour
{
    public static L6_8Player Instance;

    private int attackValue = 50;
    private int score = 0;

    public List<L6_8Enemy> enemies;

    private bool isOver = false;

    void Awake()
    {
        Instance = this;
    }

    void Update()
    {
        Shoot();
    }

    void Shoot()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        if (Input.GetMouseButtonDown(0))
        {
            if (Physics.Raycast(ray, out RaycastHit hitInfo, 1000))
            {
                //如果射击到的是敌人,附加伤害
                if (hitInfo.collider.tag == "Enemy")
                {
                    L6_8Enemy enemy = hitInfo.collider.GetComponent<L6_8Enemy>();
                    enemy.Hurt(attackValue);
                }
            }
        }
        
    }

    public void EnemyDead(L6_8Enemy enemy)
    {
        score += 1;
        L6_8UIManager.Instance.UpdateScore(score);
        enemies.Remove(enemy);
        if(enemies.Count == 0)
        {
            // 游戏胜利
            Win();
        }
    }

    void Win()
    {
        L6_8UIManager.Instance.GameResult(true);
    }

    void Lose()
    {
        isOver = true;
        L6_8UIManager.Instance.GameResult(false);
        Time.timeScale = 0;
    }

    private void OnTriggerEnter(Collider other)
    {
        if((other.tag == "Enemy") && (isOver == false))
        {
            Lose();
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// UI管理
/// </summary>
public class L6_8UIManager : MonoBehaviour
{
    public static L6_8UIManager Instance;

    public Text scoreText;
    public GameObject resultPanel;
    public Text resultText;

    void Awake()
    {
        Instance = this;

    }
    void Start()
    {
        resultPanel.SetActive(false);
    }
    void Update()
    {
        
    }
    public void UpdateScore(int num)
    {
        scoreText.text = num.ToString();
    }

    public void GameResult(bool isWin)
    {
        resultPanel.SetActive(true);
        if (isWin)
        {
            resultText.text = "你赢了";
        }
        else
        {
            resultText.text = "你输了";

        }
    }
}

摄像机Camera 

灯光

平行光相当于太阳,距离没有影响,但是旋转会影响投影方向等,可以模拟白天黑夜

面积光源可以用于类似地面等不会改变的静态物体

灯光烘焙: Window -> Rendering -> Lighting Settings -> Generate Lighting

 

Shader概念

创建材质球 -> 把Shader拖进材质球 

粒子系统

动画系统

Animator组件

 

代码控制:

 Animation窗口设置单个动画

Animator窗口设置整个动画逻辑 通过代码控制跳跃:

    private Animator animator;
    void Start()
    {
        animator = GetComponent<Animator>();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            animator.SetTrigger("Jump");
        }
    }

模型资源与动画设置

Unity中常用模型格式:fbx

根运动勾选: 

人型动画的复用性

动画事件

 通过脚本中的方法控制动画事件

 

角色控制器Character Controller

 "skin"无法拖拽调整,只能通过调整数值

        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        //Vector3 dir = new Vector3(horizontal, -9.8f, vertical);
        Vector3 dir = new Vector3(horizontal, 0, vertical);

        //characerContoller.Move(dir * Time.deltaTime * 10);
        characerContoller.SimpleMove(dir * 10);

 

如何改善角色朝向问题,以自己为前方:

导航系统

 代码驱动:

    private NavMeshAgent nmAgent;
    public Transform target;
    void Start()
    {
        nmAgent = GetComponent<NavMeshAgent>();
    }

    void Update()
    {
        nmAgent.SetDestination(target.position);

        if (Input.GetKeyDown(KeyCode.Space))
        {
            //按下空格,暂停时播放,播放时暂停
            nmAgent.isStopped = !nmAgent.isStopped;
        }
    }

Areas:

 

NavMeshObstacle障碍物组件

 Carve勾选后相当于动态障碍物

案例:鼠标右击移动

(因为用胶囊体代替了模型没有Animator相关操作,代码中注释掉了)

    private NavMeshAgent agent;
    //private Animator animator;

    void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        //animator = GetComponent<Animator>();
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(1))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

            if(Physics.Raycast(ray, out RaycastHit hitinfo, 1000))
            {
                agent.SetDestination(hitinfo.point);
                //animator.SetBool("Run", true);
                agent.isStopped = false;
            }

            //判断自己的位置到目的地之间的位置 是否小于 设定的stoppingDistance
            if (Vector3.Distance(transform.position, agent.destination) <= agent.stoppingDistance)
            {
                //animator.SetBool("Run", false);
                agent.isStopped = true;
            }
        }
    }

 Unity资源管理

 预制体实例化

    public GameObject Prefab_Cube;
    void Update()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        if((Physics.Raycast(ray, out RaycastHit hitinfo, 1000)) && (Input.GetMouseButtonDown(0)))
        {
            GameObject.Instantiate(Prefab_Cube, hitinfo.point, Quaternion.identity, transform);
        }
    }

Resource资源加载

 

prefab_Cube = Resources.Load<GameObject>("Prefab/Enemy");

数据存档

 *注意:一旦Set之后即使注释掉也可以Get到,即只要Set过,游戏即使关闭后也依然保存

    void Start()
    {
        保存数据
        //PlayerPrefs.SetString("PlayerName", "kkk");
        //PlayerPrefs.SetFloat("PlayerValue", 2.5f);
        //PlayerPrefs.SetInt("PlayerAge", 20);

        //获取数据
        string PlayerName = PlayerPrefs.GetString("PlayerName");
        float PlayerValue = PlayerPrefs.GetFloat("PlayerValue");
        int PlayerAge = PlayerPrefs.GetInt("PlayerAge");

        print(PlayerName);
        print(PlayerValue);
        print(PlayerAge);
    }
    // 这样之后再下一次执行,Name会变成jjj
    private void Update()
    {
        if (Input.GetMouseButton(0))
        {
            PlayerPrefs.SetString("PlayerName", "jjj");
        }
    }

 因此即使删除软件也有数据保存

游戏场景加载

    private void Awake()
    {
        GameObject.DontDestroyOnLoad(gameObject);
    }

声音系统

AudioListener组件

MainCamera上一定有一个固定的AudioListener组件

 AudioSource组件

 

PlayOneShot(AudioClip)可以重叠在BGM上,比较适合用于攻击、走路等音效

案例:玩家设置保存音量

 打包发布

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值