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的属性就会影响所有的游戏对象。如果要批量创建模型,最好放到预制件里。
Scripts
创建脚本,不用的方法,最好删除。
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是一个Component-Based的引擎,所有物体都是GameObject。
- Time,Input,Physics都是Unity中的全局变量。
- MonoBehaviour 是 Unity 中所有脚本的基类。
基础
- window -> general 显示 console 控制台
- 关于注释,
///
会被编译,//
不会。所以使用///
会减慢编译的速度(但不会影响执行速度)且在其它的人调用你的代码时提供智能感知。 - #region 是 C# 预处理器指令。就是将复杂的代码块折叠,是界面看起来整洁一些。
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() {
}
}
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>();
}
}
}
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);
/**
移动
*/
// 自身坐标系z轴每次移动1米
this.transform.Translate(0, 0, 1);
// 世界坐标系
this.transform.Translate(0, 0, 1, Space.World);
/**
旋转
*/
this.transform.rotation = Quaternion.Euler(3.0f, 1.2f, 1.0f);
// 自身坐标系的y轴旋转10度
this.transform.Rotate(0,10,0);
this.transform.Rotate(0,10,0, Space.World)
// 围绕世界坐标点旋转,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.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组件大。
- 动画结束后,可以添加事件(挂载的游戏对象的脚本),可以通过这个特性,使得很多动画循环播放。
动画的使用方式如下。
- 新建一个游戏物体
- 在游戏物体添加Animation组件
- 打开Animation面板 window - Animation
- 选中游戏对象,在游戏面板中,点击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
组件
练习 demo
循环创建几个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
参考资料
- 【Unity自制手册】unity常用API大全——一篇文章足以(万字详解)
- Unity 引擎渲染技术学习极简路线图
- 视频教程
- 详解Unity入门之GameObject
- 详解Unity中的ShaderGraph入门使用教程
- Unity3D开发教程:愤怒的小鸟
- Unity性能优化 - 脚本篇
- 第一天 初识Unity脚本
- unity 发送消息与web前端交互
- unity预处理器指令
- Unity Input System 新输入系统的功能及用法介绍
- Unity神奇的Gizmos 辅助线框
- 通过Mixamo生成人物动画并导入Unity实现资源可用的方法
- transform.LookAt参数详解
- Unity随记(一)LookAt和LookRotation的使用
- Unity 基础 之 Vector3介绍
- Transform.Forward和Vector3.Forward的正确使用方法
- unity与vue交互(无第三方插件)