Unity项目总结

  • 写在前面
    • 视频播放(Lua调UnityAPI)
    • AB包使用(异步加载AB包)
    • 编辑器模式运行(Editor编辑器开发)
    • Phong光照模型(顶点片元Shader、表面体Shader)
    • 人物发光特效(表面体Shader)
    • 图像渐变(固定管线Shader)
    • 商城系统(SQLite访问)
    • 3D塔防(AI寻路)
    • A*寻路算法
    • 动画系统(动画状态机)
    • 背包系统(物品拖拽)
    • 关卡选择UI界面(UGUI)
    • 3D坦克大战(物体系统)
    • 打砖块(射线检测)
    • 拾取金币(碰撞检测)
    • 行星绕恒星转动
    • 个人场景搭建
    • 滚球游戏(输入输出轴)
  • 写在后面

写在前面

个人兴趣+项目需要,学习一下Unity引擎,在此记录一下自己所作的小项目。
项目地址:https://github.com/hahahappyboy/UnityProjects
总览
视频播放(Lua调UnityAPI)

AB包使用(异步加载AB包)

编辑器模式运行 (Editor编辑器开发)

Phong光照模型(顶点片元Shader、表面体Shader)

人物发光特效(表面体Shader)

图像渐变(固定管线Shader)

商城系统(SQLite访问)

3D塔防(AI寻路)

A*寻路算法

动画系统(动画状态机)

背包系统(物品拖拽)

关卡选择UI界面(UGUI)

3D坦克大战(物理系统)

打砖块(射线检测)

拾取金币(碰撞检测)

行星绕恒星转动

个人场景搭建

滚球游戏(输入输出轴)

视频播放(Lua调UnityAPI)


注意事项:
(1)XLua使用了单例模式和自定义Loader,自定义Loader是为了重新定位Lua文件的路径,因为默认的路径是StreamingAssets目录。自定义Load函数参数和返回值是固定的写法private byte[] CustomLoader(ref string filePath)。之后使用DoString()函数调用lua语句。

public class XluaEnv {//单例private static XluaEnv _Instance = null;public static XluaEnv Instance {get {if (_Instance == null) {_Instance = new XluaEnv();}return _Instance;}}private LuaEnv _luaEnv;private XluaEnv() {_luaEnv = new LuaEnv();_luaEnv.AddLoader(CustomLoader);}//自定义Loaderprivate byte[] CustomLoader(ref string filePath) {string path = Application.dataPath;path = path.Substring(0,path.Length-7) + "/DataPath/Lua/" + filePath + ".lua";Debug.Log(path);if (File.Exists(path)) {return File.ReadAllBytes(path);} else {return null;}}//调用Luapublic object[] DoString(string code) {return _luaEnv.DoString(code);}//释放public void Free() {_luaEnv.Dispose();_Instance = null;}//获取Lua中的全局变量public LuaTable Global {get {return _luaEnv.Global;}}
}

(2)在启动脚本Bootstrap.cs使用XluaEnv.Instance.DoString("require('Bootstrap')");执行lua的启动脚本Bootstrap.lua使用 _luaBootstrap = XluaEnv.Instance.Global.Get<LuaBootstrap>("BootStrap");Bootstrap.lua中的BootStrap表映射到自定义的Unity中的结构体LuaBootstrap上,并且在Start()Update()方法中分别调用结构体的委托Start()Update()方法,这样就实现了lua的生命周期。

// lua表映射
[CSharpCallLua]
public delegate void LifeCycle();
[GCOptimize()]
public class LuaBootstrap {public LifeCycle Start;public LifeCycle Update;
}public class Bootstrap : MonoBehaviour {// private GameObject _button;//lua得Boosstrappublic LuaBootstrap _luaBootstrap;private void Start() {//防止切换场景时,脚本对象丢失DontDestroyOnLoad(gameObject);XluaEnv.Instance.DoString("require('Bootstrap')");_luaBootstrap = XluaEnv.Instance.Global.Get<LuaBootstrap>("BootStrap");_luaBootstrap.Start();}private void Update() {_luaBootstrap.Update();}
}

(3)lua中主要就是调用Unity中的API,记得使用其他lua文件变量时要require()加载一下这个lua文件。由于lua没有泛型,所以调UnityAPI的时候,一般会用其对应的太有Type参数的重载方法。例如加载AB包时的这段代码ABManager.Manifest = mainAssetBundle:LoadAsset("AssetBundleManifest", typeof(CS.UnityEngine.AssetBundleManifest)) 。lua写法其实和Unity差不多,规则就是CS.命名空间.对应变量。这里展示该项目UI界面的写法

--[[UI界面]]UIManager = {}
function UIManager:Start()-- print('ui_manager:Start')ABManager:LoadFile("prefabs")local buttonPrefab = ABManager:LoadAsset("prefabs","Button")local buttonGameObject = UIManager:Instantiate(buttonPrefab)buttonGameObject:GetComponent(typeof(CS.UnityEngine.UI.Button)).onClick:AddListener(buttonListener)
endfunction UIManager:Update()-- print('ui_manager:Update')
end-- 初始化预制体
function UIManager:Instantiate(prefab)local gameObject =  CS.UnityEngine.Object.Instantiate(prefab)gameObject.transform:SetParent(CS.UnityEngine.GameObject.Find("Canvas").transform);gameObject.transform.localRotation = CS.UnityEngine.Quaternion.identity;gameObject.transform.localPosition = CS.UnityEngine.Vector3.zero;gameObject.transform.localScale = CS.UnityEngine.Vector3.one;gameObject.name = gameObject.namereturn gameObject
end
-- button的监听
function buttonListener()local vidioPlayerPrefab = ABManager:LoadAsset("prefabs","VideoPlayer")local vidioGameObject = UIManager:Instantiate(vidioPlayerPrefab)local rectTransform = vidioGameObject:GetComponent("RectTransform");rectTransform.offsetMax = CS.UnityEngine.Vector2.zero;rectTransform.offsetMin = CS.UnityEngine.Vector2.zero;
end

AB包使用(异步加载AB包)


注意事项:
见https://blog.csdn.net/iiiiiiimp/article/details/128304154

编辑器模式运行(Editor编辑器开发)


注意事项:
(1)CubeManager脚本组件要实现特性[ExecuteInEditMode],这样其Update()方法才能在鼠标在Scene中移动/点击时调用。
(2)CubeManagerEditor编辑器脚本用于编辑CubeManager脚本组件在Inspector面板中显示什么,所以要加入[CustomEditor(typeof(CubeManager))]关联到CubeManager脚本组件,并在OnEnable()方法中获取CubeManager脚本对象cubeManager = (CubeManager)target 。在OnInspectorGUI()方法中描写要在CubeManager组件中绘制的按钮等,该方法只要每次CubeManager脚本组件值变化或则点击CubeManager脚本组件挂载的GameObject都会执行

public override void OnInspectorGUI() {Debug.Log("CubeManagerEditor:OnInspectorGUI");//显示cubeListserializedObject.Update();SerializedProperty serializedProperty = serializedObject.FindProperty("cubes");EditorGUILayout.PropertyField(serializedProperty, new GUIContent("节点"), true);serializedObject.ApplyModifiedProperties();//开始编辑按钮显示if (isEditor==false && GUILayout.Button("开始连线")) {Windows.OpenWindow(cubeManager.gameObject);isEditor = true;}//关闭编辑else if (isEditor && GUILayout.Button("结束连线")){Windows.CloseWindow();isEditor = false;}//删除最后一个节点if (GUILayout.Button("删除最后一个连线")){RemoveAtLast();}//删除所以节点else if (GUILayout.Button("删除所有连线")){RemoveAll();}
}

OnSceneGUI()方法会在当鼠标在Scene视图下发生变化时执行,比如鼠标移动、点击。发射射线也在里面,因为Input.GetMouseButtonDown(0)要在游戏运行时执行,而编辑器下游戏是没有运行的,所以鼠标监听用的是

Event.current.button == 0 && Event.current.type == EventType.MouseDown

同理从屏幕发射射线也不能用Camera.main.ScreenPointToRay(Input.mousePosition)而是用

Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);

代码

//当选中关联的脚本挂载的物体
//当鼠标在Scene视图下发生变化时,执行该方法,比如鼠标移动,比如鼠标的点击
private void OnSceneGUI() {if (!isEditor)return;//点击了鼠标左键//非运行时,使用Event类 , 不能用Input.GetMouseButtonDown(0)//Event.current.button 判断鼠标是哪个按键的//Event.current.type 判断鼠标的事件方式的if (Event.current.button == 0 && Event.current.type == EventType.MouseDown) {RaycastHit hit;//从鼠标的位置需要发射射线了//因为是从Scene视图下发射射线,跟场景中的摄像机并没有关系,所以不能使用相机发射射线的方法//从GUI中的一个点向世界定义一条射线, 参数一般都是鼠标的坐标Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);if (Physics.Raycast(ray, out hit)){if (hit.transform.tag == "Plane") {//点到的是地板GameObject prefab = Resources.Load<GameObject>("Prefabs/Cube");GameObject cube = Instantiate(prefab, hit.point+ Vector3.up, prefab.transform.rotation);cubeManager.cubes.Add(cube);}else if (hit.transform.tag == "Cube") {//点到的是CubecubeManager.cubes.Add(hit.transform.gameObject);                }}}
}

(3)之所以需要弹出一个窗口是因为当创建一个Cube过后,Unity会自动选中聚焦在这个Cube上,而在创建一个窗口并在Update()中用Selection.activeGameObject = _plane让Unity聚焦在挂有CubeManager脚本组件的Plane上。

private void Update() {Debug.Log("Windows:Update");//让选中焦点一直处于plan上,不是处于创建的cube上if (Selection.activeGameObject!= null) {Selection.activeGameObject = _plane;}
}

Phong光照模型(顶点片元Shader、表面体Shader)


注意事项:
左边是顶点片元着色器效果、右边是表面体着色器效果
(1)顶点片元着色器实现Phong光照
主纹理和法线纹理使用的是同一个float4 texcoodr:TEXCOORD0;,这是因为主纹理和法线纹理的uv坐标是一样的

r.uvMainTexture = o.texcoodr.xy * _MainTexture_ST.xy +  _MainTexture_ST.zw;
r.uvNormalTexture = o.texcoodr.xy * _BumpTexture_ST.xy +  _BumpTexture_ST.zw;

使用float3 matrixRow1 : TEXCOORD4;float3 matrixRow2 : TEXCOORD5;float3 matrixRow3 : TEXCOORD6;来存切线空间到世界空间的转换矩阵

float3 worldNormal = mul((float3x3)unity_ObjectToWorld,o.normal);
float3 worldTangent = mul((float3x3)unity_ObjectToWorld,o.tangent.xyz);
float3 worldBinormal = cross(worldNormal,worldTangent)*o.tangent.w;
r.matrixRow1 = float3(worldTangent.x,worldBinormal.x,worldNormal.x);
r.matrixRow2 = float3(worldTangent.y,worldBinormal.y,worldNormal.y);
r.matrixRow3 = float3(worldTangent.z,worldBinormal.z,worldNormal.z);

使用UnpackNormal()解出法线纹理得到切线空间下的法线,然后点乘转换矩阵矩阵,将法线的切线空间转为世界空间下。

fixed4 bumpColor = tex2D(_BumpTexture,o.uvNormalT
fixed3 bump = UnpackNormal(bumpColor);
bump *= _BumpScale;
bump.z = sqrt(1 - max(0, dot(bump.xy, bump.xy)));
bump = fixed3(dot(o.matrixRow1,bump),dot(o.matrixRow2,bump),dot(o.matrixRow3,bump));

漫反射和高光反射的法线就使用法线纹理的法线bump

//漫反射光照
fixed4  mainTextureColor = tex2D(_MainTexture,o.uvMainTexture)* _MainColor;
fixed3 diffuseColor = _LightColor0.rgb*mainTextureColor.rgb*  (dot(normalize(bump),normalize(_WorldSpaceLightPos0.xyz))*0.5+0.5);
//高光反射
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz-o.worldPos.xyz);
fixed3 reflectDir = normalize(reflect(normalize(-_WorldSpaceLightPos0.xyz),normalize(bump)));
fixed3 specularColor = _LightColor0.rgb*_SpecularColor.rgb*pow(max(0,dot(viewDir,reflectDir)),_Gloss);

最后加上自发光

fixed3 color = UNITY_LIGHTMODEL_AMBIENT.xyz * mainTextureColor.rgb + diffuseColor + specularColor;
return fixed4(color,1);

(2)表面体着色器实现Phong光照
表面体着色器比较简单
直接在pragma 里使用Lambert光照

#pragma surface surf Standard Lambert

把法线给Normal 把纹理给Albedo 即可。

struct Input
{float2 uv_MainTex;float2 uv_BumpTex;
};
void surf (Input IN, inout SurfaceOutputStandard o)
{fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;half3 n = UnpackNormal(tex2D(_BumpTex,IN.uv_BumpTex));o.Albedo = c.rgb;o.Alpha = c.a;o.Normal = n;o.Smoothness = _Glossiness;

人物发光特效(表面体Shader)


注意事项:
见https://blog.csdn.net/iiiiiiimp/article/details/127251001?

图像渐变(固定管线Shader)


注意事项:
见https://blog.csdn.net/iiiiiiimp/article/details/127170580?

商城系统(SQLite访问)


注意事项:
(1)数据库表的设计
商城表:存放物品和物品数量

装备信息表:存放装备的各个属性信息

人物信息表:存放人物的人物信息

人物装备表:存放人物的装备

(2)数据库的访问
更新用ExecuteNonQuery
返回单个结果用ExecuteScalar
返回多个结果用ExecuteReader,这里我把ExecuteReader结果用一个List<Dictionary<string, string>>存起来方便之后调用,注意执行完ExecuteReader一定要调用 _sqliteDataReader.Close();

    public List<Dictionary<string, string>> ExecuteReaderSQL(string sql) {List<Dictionary<string, string>> list = new List<Dictionary<string, string>>();_sqliteCommand.CommandText = sql;_sqliteDataReader = _sqliteCommand.ExecuteReader();while (_sqliteDataReader.Read()) {//读一行Dictionary<string, string> dictionary = new Dictionary<string, string>();for (int i = 0; i < _sqliteDataReader.FieldCount; i++) {//读这一行得一列Debug.Log(_sqliteDataReader.GetName(i)+":"+_sqliteDataReader.GetValue(i).ToString());dictionary.Add(_sqliteDataReader.GetName(i),_sqliteDataReader.GetValue(i).ToString());}list.Add(dictionary);}//关闭读取器_sqliteDataReader.Close();return list;}

(3)资源的访问
Assets/Plugins路劲下用于专门存放从外部导入的动态链接库,把sqlite3放在里面

Assets/Resources文件下用于存放图片预制体这些东西,这样就可以通过Resources.Load<>访问,如拿到预制体

 private void GetGameObject() {bagEquipPrefab = Resources.Load<GameObject>("Prefabs/BagEquip");shopEquipPrefab = Resources.Load<GameObject>("Prefabs/ShopEquip");}

数据库放在Assets/StreamingAssets文件下然后通过Application.streamingAssetsPath访问

string dataPath = "Data Source = " + Application.streamingAssetsPath + "/" + "UnitySQLite.db";

(4)装备的放置
使用Instantiate直接将装备创建在对应Box的子物体上

GameObject bagGameObject = Instantiate(shopEquipPrefab, shopWindowTransform.GetChild(shopEquipCount));

(5)装备的监听
使用 _button.onClick.AddListener(方法名);实现,这样就不用拖拽了

3D塔防(AI寻路)

注意事项:
1、怪物的生成
核心思想就是用一个类MonsterWaveMessage去存储每波怪物的信息,将这个类的对象放在一个数组里MonsterWaveMessage[],最后用遍历这个数组生成怪物即可。[System.Serializable]是让Inspector面板能显示这个类。

 [System.Serializable]public class MonsterWaveMessage {[Header("每波的时间间隔")]public float waveInterval = 1f;[Header("当前波怪物生成时间间隔")]public float monsterCreateInterval = 1f;[Header("当前波怪物数量")]public int monsterCount = 3;[Header("当前波怪物预设体")]public GameObject monsterPrefab;[Header("当前波怪物血量倍率")]public int monsterHPRate = 1;[Header("当前波怪物移动速度倍率")]public int monsterSpeedRate = 1;}

2、炮塔的射程
用的BoxCollider做的,调用OnTriggerEnter当怪物进入到就把怪物加入的一个List中,默认攻击第一个。怪物离开或死亡时时用OnTriggerExit移除List。因此怪物也要用一个List存放炮塔,好在自身死亡时通知炮塔将它移除。

进入射程

   private void OnTriggerEnter(Collider other) {if (other.gameObject.tag == "Monster") {MonsterController monster = other.GetComponent<MonsterController>();if (!_monsterList.Contains(monster)) {//添加攻击目标_monsterList.Add(monster);//Monster添加攻击炮塔monster.AddTowerController(this);}}}

离开射程

 private void OnTriggerExit(Collider other) {if (other.gameObject.tag == "Monster") {MonsterController monster = other.GetComponent<MonsterController>();if (_monsterList.Contains(monster)) {_monsterList.Remove(monster);//移除被锁定的炮塔monster.RemoveTowerController(this);}}}

3、转向怪物,才能开炮

 private float turn2Moster(MonsterController monster) {Vector3 i2monster = monster.transform.position-turretTransform.position + Vector3.up * 1f + Vector3.forward * 0.5f;Quaternion targetRoate = Quaternion.LookRotation(i2monster);turretTransform.rotation = Quaternion.Lerp(turretTransform.rotation,targetRoate,turnSmoothSpeed * Time.deltaTime);return Vector3.Angle(turretTransform.forward, i2monster);}

4、怪物被攻击和死亡
由炮塔创建的炮弹去判断与怪物的距离,如果小于0.5米就判断为击中,就销毁跟随脚本,让炮火留在原地。并且通知击中的怪物减少血量,并且播放受伤动画或死亡动画

要注意的是怪物死亡的时候需要关闭刚体,导航,碰撞体,不能只关闭碰撞体,因为导航系统也有碰撞体,不关闭会让后面的怪物以为是障碍物。

    private void Die() {//关闭导航碰撞和碰撞体和刚体Destroy(_rigidbody);_navMeshAgent.isStopped = true;_navMeshAgent.enabled = false;_capsuleCollider.enabled = false;this.liveState = LiveState.Die;//通知Tower将自己移除TowerRemoveMe();_towerControllerList.Clear();}

5、炮塔的生成
将所有炮塔设置为Tower一层,让后使用鼠标发射射线,layerMask只检测Tower这一层。

if (Input.GetMouseButtonDown(0)) {Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);LayerMask layerMask = LayerMask.GetMask("Tower");//只检测Tower层的射线if (Physics.Raycast(ray,out _raycastHit,100,layerMask) && currentChioceTowrtID != -1) {Transform tower = _raycastHit.transform;if (tower.childCount == 0) {//还没有放置炮塔GameObject tow = Instantiate(towerPrefab[currentChioceTowrtID], Vector3.zero, Quaternion.identity);tow.transform.parent = tower.transform;tow.transform.localPosition = Vector3.up * 2.7f;}}
}

6、镜头的移动
就是用相机目前移动的位置加上当前鼠标移动的方向,然后用Mathf.Clamp限定镜头在一定范围内。

if (Input.GetMouseButton(0))
{Vector3 transPosition = cameraTrans.position- Vector3.right * Input.GetAxisRaw("Mouse X") * sensitivityDrag * Time.timeScale- Vector3.forward * Input.GetAxisRaw("Mouse Y") * sensitivityDrag * Time.timeScale;cameraTrans.position = transPosition;cameraTrans.position = new  Vector3(Mathf.Clamp(cameraTrans.position.x,20,40),cameraTrans.position.y,Mathf.Clamp(cameraTrans.position.z,2, 30));
}

A*寻路算法


注意事项:
1、算法流程
2、G为当前点距离起点的估量代价

 if (i==0 || j == 0) {G = 10;} else {G = 14;
}
G += centerCube.G;

H为当前点距离终点的估量代价,(为终点坐标-当前点坐标) * 10

 H = (cubeEnd.X - currentCube.X + cubeEnd.Z - currentCube.Z) * 10;

F为F=G+H
3、中心点是用来确定下一步前进路线的,因为中心点的选取与H有关。而发现者finder是用来确定标记回去的路线的,因为发现者finder的选取与G有关且选的就是当前的中心点。

动画系统(动画状态机)

注意事项:
1、从站立到跑起来了融合树,让动作过度更加自然

动画状态机如下,使用RunSpeed(float)>0.1参数控制角色是站立还是奔跑,是使用

2、呼喊动画放置在第二层级,使用Trigger控制呼喊播放。骨骼遮罩只选择手和脑袋即可。需要注意的是,当角色呼喊后,触发条件为空且HasExitTime要勾选(一般是不勾选的),不然无法回到Empty。


3、角色静步和呼喊都是设置的虚拟按键,通过Input.GetButton("Sneak")Input.GetButton("Shout")判断是否按住虚拟键。
4、角色转身代码,即先获取角色移动的方向moveDir = new Vector3(horAxis, 0, virAxis);再把这个方向转为四元数moveQua = Quaternion.LookRotation(moveDir);最后让角色准到这个方向即可transform.rotation = Quaternion.Lerp(transform.rotation, moveQua, Time.deltaTime * turnSpeed);

virAxis = Input.GetAxis("Vertical");
horAxis = Input.GetAxis("Horizontal");
runSpeedParameter = Animator.StringToHash("RunSpeed");
if (virAxis != 0 || horAxis!= 0) {//播放动画            _animator.SetFloat(runSpeedParameter,MOVE_MAX_SPEED,0.3f,Time.deltaTime);//获取移动方向moveDir = new Vector3(horAxis, 0, virAxis);//将方向转化为四元数moveQua = Quaternion.LookRotation(moveDir);//角色转身transform.rotation = Quaternion.Lerp(transform.rotation, moveQua, Time.deltaTime * turnSpeed);} else {_animator.SetFloat(runSpeedParameter,0,0.1f,Time.deltaTime);
}

5、相机跟随代码

Vector3 followDir =cameraTransform.position - this.transform.position;
Vector3 moveDir = originalPlayer2Camera - followDir;
float moveSpeed = 3f;
cameraTransform.position =Vector3.Lerp(cameraTransform.position, moveDir + cameraTransform.position, Time.deltaTime * moveSpeed);

背包系统(物品拖拽)


注意事项:
1、装备拖动时的检测。为了检测装备是拖到哪里了,是装备栏吗?物品栏吗?等,就需要在拖动装备的时候检测鼠标移动的位置。因此在装备脚本中在OnBeginDrag把装备的raycastTarget属性关了,在OnEndDrag中再开启,这样拖动装备时eventData.pointerEnter得到的就是装备下面的UI控件了。因为如果不把raycastTarget属性关了,则eventData.pointerEnter一直检测的是拖动的装备UI。

    public void OnBeginDrag(PointerEventData eventData) {equipmentImageGetComponent.raycastTarget = false;}public void OnDrag(PointerEventData eventData) {this.transform.position = Input.mousePosition;Debug.Log(eventData.pointerEnter);}public void OnEndDrag(PointerEventData eventData) {equipmentImageGetComponent.raycastTarget = true;}

2、为了让拖动的物体显示在最上层,让其拖动时不被其他物体遮住,因此需要在拖动前OnBeginDrag重新设置一下他的父物体为画布,并且记录一下拖动前的父物体

public void OnBeginDrag(PointerEventData eventData) {//关闭射线equipmentImageGetComponent.raycastTarget = false;//拖动前的位置beginDragParentTransform = this.transform.parent;//更改变父对象,让其能显示在最上层this.transform.SetParent(canvasTransform);
}

3、拖动结束后在OnEndDrag中通过eventDatatag去判断是不是拖到了格子上或则装备上,不是的话返回原来的位置

public void OnEndDrag(PointerEventData eventData) {GameObject eventDataGameObject = eventData.pointerEnter;//放入空的装备栏 或则 空的背包栏 或则 已经装备了的背包栏或装备栏if ((eventDataGameObject.tag == "EquipBox"||eventDataGameObject.tag == "BagBox"||eventDataGameObject.tag == "Equipment") &&eventDataGameObject.transform != beginDragParentTransform) {if (eventDataGameObject.tag == "Equipment") {//已经装备了的背包栏或装备栏eventDataGameObject.GetComponent<EquipmentController>().ReceiveEquipment(this.gameObject);} else {//空的装备栏 或则 空的背包栏eventDataGameObject.GetComponent<BaseBox>().ReceiveEquipment(this.gameObject);}} else {BackToOriginalPosition();}equipmentImageGetComponent.raycastTarget = true;}

4、格子接受装备很简单,直接把装备设为其子物体就行,再让其localPosition归零。

    public override void ReceiveEquipment(GameObject equipment) {// Debug.Log(this);equipment.transform.SetParent(this.transform);equipment.transform.localPosition = Vector3.zero;equipment.GetComponent<EquipmentController>().equipmentState = BaseEquipment.EquipmentState.BagBoxing;}

关卡选择UI界面(UGUI)


注意事项:
1、关卡的摆放使用的是网格布局,先创建一个空物体,加上网格布局组件。然后将各个管卡设置为其子物体。各个关卡的初始化是使用代码初始化的,用GetChild函数隐藏或显示UI。

2、关卡边缘红色的选择框移动使用的是selectFrame.SetParent(this.transform,false);方法,false表示不会改变selectFrame的Transform组件的属性值。
3、界面的跳转用的是SceneManager.LoadScene(sceneName);跳转后需要保存的数据用了单例模式去保存

public class SceneDataManager {//单例private static SceneDataManager ins;//传输数据private Dictionary<string, object> sceneOneshotData = null;//管理星星的数量private Dictionary<int, int> starDic;public Dictionary<int, int> StarDic {get { return starDic; }}public SceneDataManager() {starDic = new Dictionary<int, int>();}public static SceneDataManager GetInstance() {if (ins == null) {ins = new SceneDataManager();}return ins;}
}

4、找物体一般用FindWithTag、GetChild、Find这些函数

5、关卡UI界面
层级要分明,想用一个创建一个空对象,把空对象的锚点设置在画面中心,然后空对象里面放UI
注意层级面板越在上面UI层级越低,就会被遮挡。

3D坦克大战(物体系统)


注意事项:
见https://blog.csdn.net/iiiiiiimp/article/details/125588752

打砖块(射线检测)


注意事项:
1、用cameraTransform = Camera.main.transform获取摄像机的位置
2、用Camera.main.ScreenPointToRay(Input.mousePosition)将鼠标坐标转化为射线
3、用Physics.Raycast(mouseRay,out hit,rayDistance)获取射线的碰撞体,然后用碰撞体位置hit.point减去摄像机位置cameraTransform.position就能得到小球发射的方法,再用Rigidbody.velocity给这个方向一个速度即可

拾取金币(碰撞检测)

注意事项:
1、金币碰撞器用的网格碰撞器并且开启触发器,刚体组件开启重力。在OnTriggerEnter方法中通过触发者的名字来判断是否与玩家(蓝色平板)发生处罚。

2、创建金币用的5个空对象CoinCreater1-5设置为一个空对象CoinCreaters的子对象,这样就能通过CoinCreaters脚本中的this.transform.GetChild(i)获取子物体了。注意不要用this.gameObject.GetComponentsInChildren<Transform>(),因为这个函数连父物体CoinCreaters也会获取到。

行星绕恒星转动


注意事项:
1、球体后面的白色伪影

2、为了让行星围绕恒星(中间红色的球)转,用了Vector3.Cross叉乘求法向量,再用this.transform.RotateAround函数。

个人场景搭建


注意事项:
1、选中摄像头,按Ctrl+Shift+F可以将摄像头快速移动到Scene画面的位置。
2、一般如果要创建一个复合物体(父子物体),那么最好用一个空物体作为根物体,把这个复合物体设为空物体的子物体,这样的好处就是整个物体的中心点就是空物体的中心点,并且避免了子物体拉伸旋转时出现变形。
例如一个椅子就可以分为腿、椅背、椅垫。

3、墙的透明材质

4、选中物体按W变为移位模式再按V可以对其进行贴合。

滚球游戏(输入输出轴)

写在后面

每个人的时间区间都不一样吧,不用太在意别人的眼光,趁着年轻还可以一无所有还可以重头再来时,多做自己想做的事情吧。

Unity项目总结(已完成17项,持续更新ing,含商城、塔防、背包、动画、坦克大战等)相关推荐

  1. 华为机试python3题解(17题 持续更新ing)

    目录 字符串 HJ1 计算字符串最后一个单词的长度 HJ2 输出输入字符串中含有该字符的个数.(不区分大小写字母) HJ4 字符串分隔 HJ9 提取不重复的整数 倒序类 HJ11 数字颠倒 HJ12 ...

  2. ECharts数据可视化项目-大屏数据可视化【持续更新中】

    ECharts数据可视化项目-大屏数据可视化[持续更新中] 文章目录 ECharts数据可视化项目-大屏数据可视化[持续更新中] 一. 数据可视化ECharts使用 二.技术栈 三.数据可视化 四.可 ...

  3. (持续更新, 目前含100+工具类) DevUtils 是一个 Android 工具库

    DevUtils Github About (持续更新, 目前含100+工具类) DevUtils 是一个 Android 工具库, 主要根据不同功能模块,封装快捷使用的工具类及 API 方法调用. ...

  4. 开源小程序CMS网站,JeeWx-App-CMS 持续更新ing~

    JeeWx-App-CMS开源小程序CMS网站,持续更新ing~  JeeWx-App-CMS 是jeewx开发的小程序网站开源项目,基于小程序wepy语言,具备cms网站的基本功能,能够打造简单易用 ...

  5. 重拾CCNA,学习笔记持续更新ing......(4)

    重拾CCNA,学习笔记持续更新ing......(4) 路由器作用功能的经典解说(笑)(非原创) 假设你的名字叫小不点,你住在一个大院子里,你的邻居有很多小伙伴,在门口传达室还有个看大门的李大爷,李大 ...

  6. 程序员的职业选择,你应该知道的,持续更新ing

    程序员的职业选择,你应该知道的,持续更新ing 一下内容只是个人认知的表达,仅供参考,互相交流,不喜勿喷 程序员的职业选择,你应该知道的,持续更新ing 我认识很多猎头,有些曾经是经验丰富的HR,以下 ...

  7. 2023届秋招提前批信息汇总(持续更新ing)

    实时更新的文档:2023届秋招提前批信息汇总(持续更新ing) (qq.com) 公司 投递链接 面试网站 工作地点 截止时间 互联网及私企(软件) 网易游戏 网易游戏(互娱)校园招聘官网 https ...

  8. 自然语言处理数据集集锦(持续更新ing...)

    诸神缄默不语-个人CSDN博文目录 最近更新时间:2023.6.27 最早更新时间:2023.4.25 文本摘要主题的数据集见我之前写的另一篇博文:文本摘要数据集的整理.总结及介绍(持续更新ing-) ...

  9. Python3常用其他API速查手册(持续更新ing...)

    诸神缄默不语-个人CSDN博文目录 最近更新时间:2023.5.11 最早更新时间:2022.6.27 运算符 + - * / 取余% 开方** 等式:= == > < >= < ...

最新文章

  1. 用法 stl_PoEdu培训第四课-C++之STL
  2. mysql 存储过程与函数_12 MySQL存储过程与函数
  3. 【Android】BroadCast广播机制应用与实例
  4. C#语法之fixed 语句
  5. 下拉框_教你封装 Element Tree 树状下拉框
  6. 练习图200例图纸讲解_【宅家数学课23】经典微课6:苏教版六年级下册比例尺典型例题选讲及练习(含答案)...
  7. 嘀嗒还是滴答_2021年顺风车车主口碑榜!滴滴、滴答、一喂顺风车成TOP3
  8. 今天我不想发文了,对不起各位!
  9. Application_Error
  10. 调用腾讯的API接口
  11. 极客大学架构师训练营 大数据 三驾马车 GFS、MapReduce、BigTable,Hadoop HDFS 第23课 听课总结
  12. 社交网络模型及属性介绍
  13. 深度linux升级,测试从Deepin Linux 15升级到Deepin Linux v20
  14. 输入等值线参数绘制等值线图python_专题复习:等值线(上)
  15. (超详细)搜索软件Everything的安装与使用
  16. 【Copy攻城狮日志】飞浆学院强化学习7日打卡营-学习笔记
  17. python 报错traceback怎么解决_浅谈python出错时traceback的解读
  18. 天地图聚合 java_关于天地图的瓦片下载
  19. 【VS2017】【Windows SDK】【MSB803】找不到 Windows SDK 版本10.0.17134.0的解决办法
  20. 海豚客服系统接入技巧分享:微信端和网页端

热门文章

  1. 使用VM安装Centos7虚拟机
  2. 揭秘持牌消费金融机构的“潜规则”
  3. 安装zsh并修改配置
  4. 青蛙跳石头java_Java青蛙跳台阶问题的解决思路与代码
  5. 苹果计算机的桌面图是什么情况,苹果电脑开机后,只能显示电脑桌面,桌面图标都不能显示。怎么办?...
  6. 单片机:AT89s52 定时器 time0
  7. python把日期数据转换成数字_python3时间datetime如何转换成数字?
  8. c语言微信昵称大全女生,微信昵称女生大全(精选210个)
  9. Jexl表达式引擎(2)
  10. Spring框架AOP原理及实现