# 官方文档:https://docs.unity3d.com/ScriptReference/index.html
Part 1
基本操作
1.左下角Project面板中Assets下创建Image,Music,Scenes,Script四个文件夹,Packages不用管
2.在Scenes文件夹下新建几个unity场景文件,把预先备好的素材拉入Image与Music
3.选中Image下第一张图,例如BIRDS_1,右边Inspector面板中设置Sprite Mode为Multiple,点sprite Editor,在新窗口中点Slice并保存
4.回来Image文件夹下会看到BIRDS_1可以再细分许多子图并且已经自动命名
5.其他图片一样操作,至此素材导入完成
6.点击对应的Scene,把所需图片拉到左上方Hierarchy面板下,每个Scene都会自带一个Main Camera
7.在中间上方的Scene面板中摆放图片与摄像机的位置,操作方法跟小学时学的3DMAX差不多
8.点击正上方运行按钮,在正下方的Game面板中观察摄像机的映像,当然此时未设置动画
9.最后就是不断给场景中的对象在右边Inspector面板中add component,完
Part 2
Inspector面板
1、可见,对象ID(对应代码),静态,Tag,Layer(新建一个层把用到的素材全部拉进去)
2、Transform是本对象的资态:世界XYZ绝对坐标,俯仰角/偏航角/翻滚角,XYZ缩放比例
3、Sprite Renderer:Sprite设置素材源,Order in Layer可以设置在本Layer中的等级,值越大越靠前显示
4、其他的Component就需要点击最下面的Add Component添加,添加脚本是直接输入脚本名确认,建议是脚本名与对象名能够对应
5、以下面的代码为例,有四个响应事件的API,还有三个局部变量,凡是设成public的变量可以在Inspector面板中直接更改他的值
//Bird.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Brid : MonoBehaviour{
private bool isClick=false;
public Transform rightPos;
public float maxDis=3;
private void OnMouseDown(){//对本对象鼠标按下左键时的事件
isClick=true;
}
private void OnMouseUp(){//对本对象鼠标释放左键时的事件
isClick=false;
}
private void Update(){//本对象每帧显示前的事件
if(isClick){
transform.position=Camera.main.ScreenToWorldPoint(Input.mousePosition);//重点!
transform.position+=new Vector3(0,0,-Camera.main.transform.position.z);
if(Vector3.Distance(transform.position,rightPos.position)>maxDis){
Vector3 pos=(transform.position-rightPos.position).normalized;//方向不变长度变成1
pos*=maxDis;//让向量乘上最大距离得到相对于基点的向量
transform.position=pos+rightPos.position;//偏移向量+基点向量
}
}
}
private void Start(){//本对象第一帧显示前的事件
//可以设置初始姿态什么的
}
}
//这里要对上面的“重点”解释一下:
//屏幕按下对象有Input.mousePosition,然后转换成世界坐标得到transform.position,transform就是本对象
//2D场景平行视角摄像机默认坐标是(0,0,-10),摄像头面向Z轴正向,坐标转换后的坐标是落在摄像头所在摄影平面的!
//摄像机对用户触摸屏幕点坐标的转换时XY坐标可以正确转换,但是Z坐标会变成取摄像机的坐标,所以z坐标要减去摄像机z坐标
Part 3
Rigidbody 2D:当Boty Type为Dynamic时绑定动力学(如自由落体等)引擎,例如小鸟,angular drag是滚动摩擦系数
Rigidbody 2D:当Boty Type为Kinematic时绑定不受动力学引擎,例如用户拖动小鸟时
(当然用户拖动时用Dynamic也OK,反正脚本中重设了对象坐标,但是这样释放时飞出去的速度会很大)
Rigidbody 2D:当Boty Type为Static时绑定可以作为Spring Joint 2D的Connected Rigid Body参数的值(弹簧另一端),例如树枝
Spring Joint 2D:绑定弹簧关节引擎,Connected Rigit Body参数是弹簧的另一端,Frequnecy是阻尼系数
Circle Collider 2D:绑定圆形碰撞引擎,Radius参数是响应半径
Box Collider 2D:绑定矩形碰撞引擎,Edit Collider可以设置响应边框
Part 4 小鸟飞出
private SpringJoint2D sp;
private Rigidbody2D rg;
private void Awake(){
sp=GetComponent<SpringJoint2D>();
rg=GetComponent<Rigidbody2D>();
}
private void OnMouseDown(){
isClick=true;
rg.isKinematic=true;//不受动力学控制,可以让用户自由拖动
}
private void OnMouseUp(){
isClick=false;
rg.isKinematic=false;//受动力学控制,计算受力改变其速度
Invoke("Fly",0.1f);//计算受重力与弹簧力0.1秒之后执行Fly函数
}
private void Fly(){
sp.enabled=false;//弹簧失效,注意Frequency的值是阻尼系数
}
Part 5 场景
对THEME_01_THEME_1中进行slice得到地面拉到场景中
Add Componenet-Box Collider 2D-Edit Collider把碰撞框上边线往下拉一点让鸟在草上滚
新建env文件夹把THEME_01_THEME_1_0放进去,ctrl+d复制多份并在Scene面板中往右拉铺满整个场景
然后再把小草与天空放上来即可
Part 6 猪
//BIRDS_1_103拉进去场景并更改ID为pig
//添加Circle Collider 2D,按Edit Collider调整大小
//添加Rigidbody 2D,调整 Angular Drag为2
//添加Add Component,添加下面代码
public class Pig : MonoBehaviour{
public float maxSpeed=5.0f;
public float minSpeed=3.0f;
private SpriteRenderer render;//精灵渲染类对象声明
public Sprite hurt;
private void Awake(){
render=GetComponent<SpriteRenderer>();//定义对象为绑定的本对象
}
private void OnCollisionEnter2D(Collision2D collision){//碰撞检测
print(collision.relativeVelocity.magnitude);
if(collision.relativeVelocity.magnitude<minSpeed){
return ;
}
else if(collision.relativeVelocity.magnitude>maxSpeed){
Destroy(gameObject);
}else if(collision.relativeVelocity.magnitude<maxSpeed && collision.relativeVelocity.magnitude>minSpeed){
render.sprite=hurt;
}
}
}
//回到Inspector面板中调整上面三个public参数,hurt的值是BIRDS_1_104图片
part 7 弹弓
//左树枝left_branch加Line Renderer并设材质,在其下创建空对象并更名为leftPos
//右树枝right_branch加Line Renderer并设材质,在其下创建空对象并更名为rightPos(前面画弹簧建了可跳过)
//在Bird类中加入四个public成员,并在Inspector面板中拖动赋值
public LineRenderer right;//这四变量是树枝对象的子类对象,要在Inspector面板中赋值
public LineRenderer left;
public Transform leftPos;
public Transform rightPos;
//在update函数处加入四行代码
right.SetPosition(0,rightPos.position);
right.SetPosition(1,transform.position);
left.SetPosition(0,leftPos.position);
left.SetPosition(1,transform.position);
part 8 死亡特效
拖死亡动画第一帧到场景,重命名为Boom
增加Boom.cs脚本
public class Boom : MonoBehaviour{
public void destroying(){
Destroy(gameObject);
}
}
增加Animator,ctrl+6打开动画编缉器,按create创建一个动画,放到Assets下的Animation下(无就新建)
依次拖入关键帧,并在最后一帧添加事件destroying,把场景中的Boom拖动到Assets下的prefabs下(无就新建)
在Pig.cs中,猪destroy函数后加上Instantiate(boom,transform.position,Quaternion.identity);
类成员中加上public GameObject boom;,回到Inspector面板把prefabs下的Boom拉到脚本赋值处
part 9 分数特效
拖分数到场景重命名为pigScore,再拖动到prefabs下,在Pig.cs中,在上面给死亡动画Instantiate下再写:
GameObject go=Instantiate(score,transform.position+new Vector3(0,0.8f,0),Quaternion.identity);
Destroy(go,1.5f);
在声明类成员变量时写public GameObject score;,回到Inspector面板把prefabs下的pigScore拉到脚本赋值处
part 10 游戏管理(重难点)
//创建新的空类GameManager,往GameManager.cs中写入
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour{
public List<Bird> birds;
public List<Pig> pigs;
public static GameManager _instance;
private void Awake(){
_instance=this;
}
private void Start(){
Initialized();
}
private void Initialized(){
for(int i=0;i<birds.Count;i++){
if(i==0){
birds[i].enabled=true;
birds[i].sp.enabled=true;
//birds[i].cc.enabled=true;
}else{
birds[i].enabled=false;
birds[i].sp.enabled=false;
//birds[i].cc.enabled=false;
}
}
}
public void NextBird(){
if(pigs.Count>0){
if(birds.Count>0){
Initialized();
}else{
print("lose");
}
}else{
print("win");
}
}
}
//往inspector面板中按顺便拖入鸟与猪
//Bird.cs中设置在Fly()后触发Next()唤醒下一只鸟
private void Fly(){
sp.enabled=false;//弹簧失效,注意Frequency的值是阻尼系数
Invoke("Next",5);
}
private void Next(){
GameManager._instance.birds.Remove(this);
Destroy(gameObject);
Instantiate(boom,transform.position,Quaternion.identity);
GameManager._instance.NextBird();
}
//至此游戏主体实现完成!!!
part 11 上树
GameManager.cs的Awake中加上originPos=birds[0].transform.position;
GameManager.cs的Initialized中加上birds[0].transform.position=originPos;
Bird.cs的OnMouseUp末加上right.enabled=false;left.enabled=false;
part 12 弹弓
Bird.cs的Update中SetPosition前加上right.enabled=true;left.enabled=true;
part 13 木块
1. 插入多种木块图
2. 创建Rigidbody 2D使用物理学引擎如自由落体
3. 创建Box Collider 2D使用盒式碰撞引擎
4. 使用pid脚本并赋hurt/score/ispid参数
5. copy多份
part 14 胜负界面
在Hierarchy界面下创建UI-Image重命名为lose,是全屏黑,不透明度一半
在lose下创建UI-Image重命名为menu,是左1/3屏黑,不透明度0
在menu创建UI-Image重命名为retry,插图是重玩按扭图片
在menu创建UI-Image重命名为exit,插图是退出按扭图片
(千万不要拉到prefabs下!)
在Hierarchy界面下创建UI-Image重命名为win,是全屏黑,不透明度一半
在lose下创建UI-Image重命名为menu,是左1/3屏黑,不透明度0
在menu创建UI-Image重命名为next,插图是下一关按扭图片
在menu创建UI-Image重命名为exit,插图是退出按扭图片
(千万不要拉到prefabs下!)
修改GameManager.cs
声明变量public GameObject Win;并把lose拉过来属性面板赋值
声明变量public GameObject Loss;并把lose拉过来属性面板赋值
在输出失败后加上Loss.SetActive(true);
在输出成功后加上Win.SetActive(true);
(补充说明下layer与sorting layer:)
sorting layer用于渲染,Unity渲染关系层级顺序:Camera -> sorting layer -> sorting order
layer用于碰撞检测,光线投射
part 15 按钮事件(重玩/下一关/退出)
File-Build Setting-把全部unity场景文件拉上去
在GameManager.cs中写三个函数
public void Next(){SceneManager.LoadScene(3);}
public void Replay(){SceneManager.LoadScene(2);}
public void Home(){SceneManager.LoadScene(1);}
给三个按钮Add Component-OnClick,把GameManager拉过来,再选对应的函数
part 16 镜头跟随
改摄像机的x坐标,涉及的接口:
static function Lerp (from : Vector3, to : Vector3, t : float) : Vector3
---->返回值=from+(to-from)*t
Mathf.Clamp(float value,float min,float max)
---->在 Mathf.Clamp 中传入三个参数:value,min,max,限制 value的值在min,max之间,如果value大于max,则返回max,如果value小于min,则返回min,否者返回value;
Time.deltaTime
---->每帧间隔时间,如20帧每秒,即0.05秒/帧,则Time.deltaTime=0.05
写法一:Camera.main.transform.position=new Vector3(Mathf.Clamp(posX,0,15),Camera.main.transform.position.y,Camera.main.transform.position.z);
缺点:跟随可以实现,但是换鸟是会突然转变
写法二:float posX=transform.position.x;
Camera.main.transform.position=Vector3.Lerp(Camera.main.transform.position,\
new Vector3(Mathf.Clamp(posX,0,15),\
Camera.main.transform.position.y,\
Camera.main.transform.position.z),\
3*Time.deltaTime);
这个函数的意义:相机初始坐标0,0,-10,小鸟坐标的x在大于0时,返回值=from+(to-from)*t!=from产生跟随,且t是3*Time.deltaTime小于1所以不是即时跟随,平滑换鸟
3这个值的意义是1秒移动3米,太小的话相机跟不上,太大的话换鸟时会突然变化镜头位置,要自己调参得出
part 17 加载音乐
public AudioClip birdCollision;
if(collision.gameObject.tag=="Player")AudioSource.PlayClipAtPoint(birdCollision,transform.position);
part 18 黄鸟
//换贴图
//写脚本Bird_yellow.cs继承
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bird_yellow : Bird{
private bool fly_again;
private new void Awake(){
base.Awake();
fly_again=false;
}
private new void OnMouseDown(){
if(isFlying){
if(!fly_again){
rg.velocity*=2;
fly_again=true;
}
return ;
}
if(!isClick)AudioSource.PlayClipAtPoint(select,transform.position);
isClick=true;
rg.isKinematic=true;
}
}
//在面板重新赋值,GameManager也要赋
//测试参数
part 19 绿鸟
//换贴图
//写脚本Bird_green.cs继承
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bird_green : Bird{
private bool fly_again;
private new void Awake(){
base.Awake();
fly_again=false;
}
private new void OnMouseDown(){
if(isFlying){
if(!fly_again){
Vector3 speed=rg.velocity;
speed.x*=-1;
rg.velocity=speed;
fly_again=true;
}
return ;
}
if(!isClick)AudioSource.PlayClipAtPoint(select,transform.position);
isClick=true;
rg.isKinematic=true;
}
}
//在面板重新赋值,GameManager也要赋
//测试参数
part 20 开始菜单
开场场景:插背景图,插UI-Button(进入游戏按钮与退出游戏按钮),绑定下面写的两个方法
创建GameManager对象写GameManager.cs
public class StartMenuManager : MonoBehaviour{
public void Enter(){
//print("enter");
UnityEngine.SceneManagement.SceneManager.LoadScene(1);
}
public void Exit(){
//print("exit");
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
}
part 21 关卡菜单
关卡场景:插背景图,关卡1按钮图片,关卡2按钮图片,关卡2按钮图片,返回按钮图片
记住全部添加Collider方法(Box或Circle都行),各自写一个类响应OnMouseDown()
以LevelPageExit为例:
public class LevelPageExit : MonoBehaviour{
public void OnMouseDown(){
UnityEngine.SceneManagement.SceneManager.LoadScene(0);
}
}
笔记整理
-
Inspector面板各项属性
Transform是场景每个对象的姿态
Sprite Renderer是有贴图的对象
Circle Collider 2D圆形碰撞检测
Box Collider 2D矩形碰撞检测
Rigidbody 2D是刚体引擎
(Static就是不动的,例如弹簧的另一端树杈)
(Dynamic有受力分析,如质量,滑动摩擦系数,滚动摩擦系数,重力加速度等,但不包括Collider碰撞)
(Kinematic没有受力分析,只可以通过变换进行操作,例如用户手指拖动)
Spring Joint 2D弹簧引擎
(弹簧另一端要是Rigidbody属性)
Script自己写的脚本
Line Renderer线形渲染 -
获取他人与自己的坐标
自己坐标:transform.position,其实是省略了this,或者说self
他人坐标:public List birds;;
private Vector3 originPos;
originPos=birds[0].transform.position; -
开关其他对象的引擎
在Bird.cs中
[HideInInspector]
public SpringJoint2D sp;
private void Awake(){sp=GetComponent();}
在GameManager.cs中
public List birds;
birds[i].enabled=true;//第i只鸟的Bird.cs脚本生效
birds[i].sp.enabled=true;//第i只鸟的SpringJoint2D属性生效
birds[i].enabled=false;//第i只鸟的Bird.cs脚本失效
birds[i].sp.enabled=false;//第i只鸟的SpringJoint2D属性失效 -
开关自身物理引擎(参考bird.cs)
private Rigidbody2D rg;//默认Dynamic
private void Awake(){rg=GetComponent();}
rg.isKinematic=true;//不受动力学控制,可以让用户自由拖动
rg.isKinematic=false;//受动力学控制,计算受力改变其速度 -
碰撞检测并取相对速度(参考pig.cs)
private void OnCollisionEnter2D(Collision2D collision){print(collision.relativeVelocity.magnitude);} -
改变自身贴图(参考pig.cs)
public Sprite hurt;//hurt手动在面板传入
private SpriteRenderer render;
private void Awake(){render=GetComponent();}
render.sprite=hurt;
如果要通过外部来改变贴图,请参考第3点 -
动画制作(参考boom.cs)
拖一帧到场景
ctrl+6创建动画在Animation文件夹中
给此对象写脚本
给关键帧添加触发事件,例如自我销毁
public void destroying(){Destroy(gameObject);}
把对象拖进Prefabs文件夹中,场景对象设为失效 -
创建与销毁别的对象(参考pig.cs)
public GameObject boom;//boom手动在面板传入
Instantiate(boom,transform.position,Quaternion.identity);//原地生成爆作
public GameObject score;//score手动在面板传入
GameObject go=Instantiate(score,transform.position+new Vector3(0,0.8f,0),Quaternion.identity);//头顶生成分数
Destroy(go,1.5f);//1.5秒后销毁 -
按钮跳转(参考GameManager.cs)
在场景中创建UI-Image
绑定Button事件
绑定GameManager对象
绑定对应事件public void Replay(){SceneManager.LoadScene(场景ID);} -
音乐播放
public AudioClip birdCollision;
if(collision.gameObject.tag==“Player”)AudioSource.PlayClipAtPoint(birdCollision,transform.position); -
面向对象
封装:
public可任意访问,可被子类对象继承,包括Inspector面板传入的参数
protected可被自已访问,可被子类对象继承,不会在Inspector面板中显示
private可被自已访问,不可被子类对象继承,不会在Inspector面板中显示
继承:
基础红鸟,派生出黄鸟与绿鸟(新增额外技能)
多态:
每个场景中的GameManager要用List维护所有的鸟,包括红鸟,黄鸟,绿鸟,可以声明列表元素是基类Bird -
点击事件
方法一:拉图片,加碰撞属性(必须!),自定义一个类,实现OnMouseDown()方法
方法二:创建UI-Image,贴图赋值,加Button属性,拉入本场景Manager对象(绑定类中已实现各按钮方法),选择对应方法
记得在Edit-Preference-GI Cache处改缓存路径