目录
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的区别:没有填充物(类似于控制浏览器放大缩小的滑动条)
DropDown组件
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;
}
}
Navigation面板
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上,比较适合用于攻击、走路等音效