Unity3D一些项目经验
U3D基础篇
经过了入门篇的打磨与体验,接下来进行基础篇的学习,其中包含动画,场景等设计。抓紧开始你的游戏之旅吧!
文章目录
- U3D基础篇
- 天空盒Skybox
- 动画初步
- 动画状态机
- 地形系统Terrain
- 一些经历
- 1.调节游戏的运行参数
- 2.改进Update方法使其适应设备刷新率
- 3.角色移动
- 4.角色跳跃
- 5.CineMachine锁定摄像头
- 6.受击代码模板
- 7.生成怪物(面向对象的封装,继承和多态)
- 8.切换与重置场景
- 9.2D场景光效
- 10.使用PolyBrush绘制地图
- 11.实现鼠标移动事件
- 12.单例模式鼠标实现玩家移动
- 13.修改鼠标的指针模型
- 14.迷雾效果和Post Processing
- 15.动画树Blend Tree
- 16.Shader Graph遮挡剔除
- 17.从0开始创建一个怪物
- 18.追踪攻击敌人
- 19.怪物追踪与镜头视角修改
- 20.怪物的状态切换逻辑
- 21.怪物的随机巡逻点以及停留
- 22.使用ScriptableObject处理游戏数值
天空盒Skybox
天空盒:本质是一种Materials材质,Shader为Skybox/6 sided
。
设置方式:
- 1.准备天空盒材质
- 2.Window—>Rendering—>Lighting—>Environment—>Skybox Material,拖入材质即可。
理解:可以理解成3D世界中游戏的背景,本质是上下左右前后的六张贴图。
创建天空盒:
- 1.准备六个采用
无缝设计
的贴图,并将贴图的的WrapMode=Clamp
(无缝连接)完成设置后apply。 - 2.创建一个Material,设置
Shader=Skybox/6 Sided
,(锁定Inspector),将贴图对应拖入skybox即可。
宇宙与行星案例:
步骤:
- 1.设置天空盒
- 2.添加地球,倾角23.5°
- 3.地球自转
- 4.摄像机围绕地球旋转
- 5.背景音乐
动画初步
创建动画文件:
- 1.在Anim文件夹下创建一个
Animation
文件。 - 2.修改其类型为
Legacy
文件(旧版的,易上手),具体操作为右键检查器,切换为Debug
模式,勾选Legacy即可。 - 3.将其作为一个Animation组件挂载到游戏对象上即可。
调出Animation窗口,实际上该窗口与一些剪视频的软件布局极其相似,可以类比理解。
观察运动:
在观察物体运动时,可以采用以下两种方式:
- 1.Dopesheet简报模式:Pr显示模式,更倾向数值
- 2.Curves曲线模式:更倾向观察变换趋势与运动状态变化
F键:完全显示所有曲线,或者选中的关键帧。
练习:将物体改为匀速运动:
- 1.进入
Recording Mode
开始编辑 - 2.左侧,选中Rotation.x,只显示一条X曲线
- 3.编辑关键帧,分别选中第0帧和第60帧:
右键--->Both Tangent--->Linear
曲线的编辑:
对关键帧左右进行操作:
- Free:自由调整(贝塞尔曲线)
- Linear:直线
- Constant:固定值
子节点的动画:
案例:
- 1.导入一个直升机的模型
- 2.添加一个 Legacy 动画
- 3.为主翼 Top_Rotor和尾翼 Back_Roto r添加旋转效果。
动画事件Animation Event:
- 1.给物体添加一个脚本,Ball.cs
- 2.在脚本中添加一个回调函数
public void DropOnGround(float value)
{Debug.Log("小球降落到最低点");
}
- 3.在动画编辑窗口,选中某一帧,右键选择
Add Animation Event
,右侧选择回调,设定参数的值即可。参数的类型可以是 float,int,string,GameObject等等。
动画状态机
动画状态机:一个基于状态机控制的动画系统。
状态机:一个事物或系统,有多种状态,控制它在各种状态间相互转换的机制。
案例:
一个人物模型存在以下几种状态:静止,走,跳,泅渡,攻击。
状态机的使用:
- 1.添加一个物体,机器人Robot
- 2.创建一个资源 AnimatorController
- 3.将Controller状态机挂载到物体Robot上
Animator窗口中,按F键可完全显示所有状态方块。
状态机的状态:
- Any State:
- Entry:状态机起点 ※
- Exit:
状态转换:
设置默认状态:右键某状态—>Set As Layer Default State
创建一个状态过度:右键某状态—>Make Transition
绑定动作:
- 1.准备好一个动画文件,选中某个状态
- 2.将动画文件拖入状态机某个状态的Motion下
- 3.选中Robot物体,在Animation窗口编辑舞蹈动画
- 勾选
Loop Time
表示循环播放
- 勾选
- 类型不要勾选
Legacy
- 类型不要勾选
状态参数与动画过渡:
如何判断物体应该过渡到某状态了呢?
- 1.在状态机左侧添加一个状态变量,类型可以是int,float,bool等
- 2.选中状态转移之间的线,取消勾选Has Exit Time(暂时不用,表示时间到了自动发生)
- 3.在Conditions中添加变量条件即可,当变量条件满足时,会进行状态的转移
Exit Time:
设置方法:
- 1.勾选Has Exit Time
- 2.设置退出时间
Exit Time
- 3.取消勾选
Fixed Duration
,如果勾选则单位按秒计算;否则,按圈计算。
状态机相关API:
//状态机相关运行,由状态变量来控制
//拿到Animator组件
Animator animator = GetComponent<Animator>();
//varName = value
animator.SetBool(varName,value);
animator.setInt(...);
...
状态机行为:
状态机行为:即立足于状态机上的回调函数
存在以下几种情况的回调:
- OnStateEnter():进入该状态时
- OnStateUpdate():进入该状态,每一次update()以后
- OnStateExit():退出该状态时
应用:在操控完对象某种状态时,将状态变量归未,防止重复进入某状态的bug。
更精细的控制:
问题提出:当机器球处在变身状态时,本应不能移动,但在该项目中仍可以移动,显然是一个问题。
解决方法:
//方法一:
//获取当前状态的API
anim.GetCurrentAnimatorStateInfo(0).IsName("状态名");
//方法二:
//自己添加一个额外的状态变量进行控制
地形系统Terrain
地形系统:山川河流,地表地貌
地表Terrain Layer:
- 新建一个地表Terrain Layer,相当于Material
- 指定贴图:Diffuse主帖图 + Normal 法线贴图
- 在Terrain编辑器中,选择第二个工具按钮绘制地形
- 下拉列表中选择
Paint Texture
- 选择
Edit Terrain Layers
,添加进创建的TerrainLayer即可。 - 绘制地表材质
花草:
- 选择
Paint Details
- 导入花草的贴图
一些地图渲染的细节:
- 1.此处的花草是2D贴图,并不是3D模型,用以降低GPU的负担
- 2.在离摄像机较近时,会自动扭转以正面面对摄像机
- 3.在离摄像机较远时,花草细节不被渲染
树木:
- 1.准备树的模型资源,添加树(选择 Paint Trees)
- 2.设置种植参数,批量种树即可。
- 3.给树添加碰撞检测
山峦:
- 在Paint Terrain中,设置为
Ralse or Lower Terrain
- 手动批量造山即可
盆地:
- 在Paint Terrain中,设置为
Set Height
- 手动批量造坑即可
按住shift键可以降低地势。
一些经历
1.调节游戏的运行参数
//设置游戏运行帧数
Application.targetFrameRate = ?;
//退出游戏
Application.Quit();//暂停和复原游戏函数
void PauseGame(){//调出暂停UIPauseManu.SetActive(true);//设置时停Time.timeScale = 0f;
}
void ResumeGame(){//调整暂停UIPauseManu.SetActive(false);//设置时停Time.timeScale = 1f;
}
2.改进Update方法使其适应设备刷新率
void FixedUpdate(){}
3.角色移动
//此处应拖入玩家类上的刚体组件
public RigidBody2D rigidBody;
//拖入玩家类上的动画组件
public Animator anim;void Movement(){float horizontalMove = Input.GetAxis("Horizontal");//Debug.Log(horizontalMove);[-1,1]float faceDirection = Input.GetAxisRaw("Horizontal");//Debug.Log(faceDirection);{-1,0,1}//角色移动if (horizontalMove != 0){rigidbody.velocity = new Vector2(horizontalMove * speed, rigidbody.velocity.y) ;anim.SetBool("isRun", true);}else{anim.SetBool("isRun", false);}//角色转向if (faceDirection != 0){transform.localScale = new Vector3(faceDirection, 1, 1);}}
4.角色跳跃
void Update(){Movement();SwitchAnim();}void Movement(){//角色跳跃if (Input.GetButtonDown("Jump")){rigidbody.velocity = new Vector2(rigidbody.velocity.x, jumpForce);anim.SetBool("isJump", true);} }void SwitchAnim(){anim.SetBool("idle", false);if (anim.GetBool("isJump") && rigidbody.velocity.y < 0){anim.SetBool("isJump", false);anim.SetBool("isFall", true);}else if (coll.IsTouchingLayers(ground)){anim.SetBool("isFall", false);anim.SetBool("idle", true);}}
5.CineMachine锁定摄像头
1.添加组件Cinemachine:
Windows—>PackageManager—>搜索安装即可
2.添加虚拟摄像头:
GameObject—>Cinemachine—>Visual Cemera
3.设置参量:
1)设置摄像头跟随follow
2)设置摄像头移动边界:添加Cinemachine Confiner 2D,并设置Bounding Shape 2D,一般需要给背景设置碰撞体检测,使其视像头绑定在规定边界。
6.受击代码模板
//消灭怪物private void OnCollisionEnter2D(Collision2D collision){if (collision.gameObject.tag == "Enemy"){if (anim.GetBool("isFall")){Destroy(collision.gameObject);rigidbody.velocity = new Vector2(rigidbody.velocity.x, jumpForce);anim.SetBool("isJump", true);}else if(transform.position.x < collision.gameObject.transform.position.x){rigidbody.velocity = new Vector2(-10, rigidbody.velocity.y);isHurt = true;}else if (transform.position.x > collision.gameObject.transform.position.x){rigidbody.velocity = new Vector2(10, rigidbody.velocity.y);isHurt = true;}}}
7.生成怪物(面向对象的封装,继承和多态)
在未来在制作繁多种类的怪物时,可以抽取其共同特征来形成一个父类,后面直接通过父类生成具有各自特征的子类能大大降低代码的耦合度,从而时程序效率更高。
案例:怪物父类
public class Enemy : MonoBehaviour
{//动画protected Animator anim;//音效protected AudioSource deathAudio;//虚拟的protected virtual void Start(){anim = GetComponent<Animator>();deathAudio = GetComponent<AudioSource>();}//通用方法——死亡public void Death(){deathAudio.Play();Destroy(gameObject);}
}
在子类中调用:
public class Zombies : Enemy
{protected override void Start(){//调用父类Enemy的Start方法base.Start();}
}
8.切换与重置场景
//加入场景相关库
using UnityEngine.SceneManagement;
//重置游戏逻辑
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
//切换下一关
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex+1);
9.2D场景光效
- 切换物体的材质为
Default-Diffuse
,也可自建材质为Diffuse。 - 添加Light组件,即可照亮以上材质光照效果
- 也可以给玩家本身添加点光源,使其范围呈现动画效果的扩散与缩小。
10.使用PolyBrush绘制地图
- 在
Package Manager
中引入Polybrush
插件,并导入Shader Examples(URP)
。 - 在
Tools
中开启 Polybrush的窗口,其中可以进行 地形绘制,地面平滑,颜色绘制,场景绘制等等。
11.实现鼠标移动事件
在脚本上属性拖拽为玩家结点以及 Nav Mesh Agent 的 destination。
[System.Serializable]
public class EventVector3 : UnityEvent<Vector3>
{}public class MouseManager : MonoBehaviour
{//射线碰撞到的物体信息RaycastHit hitInfo;//鼠标点击事件public EventVector3 OnMouseClicked;void Update(){SetCursorTexture();MouseControl();}//设置指针的贴图void SetCursorTexture(){//从主摄像机的位置发射一道射线到鼠标所在位置Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//将射线ray射到的物体返回给hitInfo变量if (Physics.Raycast(ray,out hitInfo)){//切换鼠标贴图}}void MouseControl(){//点击鼠标左键并且点击位置存在物体if (Input.GetMouseButtonDown(0) && hitInfo.collider != null){//点击到地面则操作挂载到人物上的事件if (hitInfo.collider.gameObject.CompareTag("Ground")){//判断事件是否为空,不为空则执行InvokeOnMouseClicked?.Invoke(hitInfo.point);}}}
}
12.单例模式鼠标实现玩家移动
在鼠标控制类上:MouseManager
using System;//添加代理public static MouseManager Instance;//添加鼠标点击事件public event Action<Vector3> OnMouseClicked;void Awake(){if (Instance != null){Destroy(this.gameObject);}Instance = this;}
在控制玩家的类上:PlayerController
using UnityEngine;
using UnityEngine.AI;public class PlayerController : MonoBehaviour
{private NavMeshAgent agent;void Awake(){agent = GetComponent<NavMeshAgent>();}void Start(){//给鼠标添加一个事件函数MouseManager.Instance.OnMouseClicked += MoveToTarget; }public void MoveToTarget(Vector3 target){agent.destination = target;}
}
13.修改鼠标的指针模型
//鼠标变换图标public Texture2D point, doorway, attack, target, arrow;//设置指针的贴图void SetCursorTexture(){//从主摄像机的位置发射一道射线到鼠标所在位置Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//将射线ray射到的物体返回给hitInfo变量if (Physics.Raycast(ray,out hitInfo)){//切换鼠标贴图switch (hitInfo.collider.gameObject.tag){case "Ground":Cursor.SetCursor(target, new Vector2(16, 16), CursorMode.Auto);break;}}}
14.迷雾效果和Post Processing
- 在Lighting界面勾选
fog
并在场景中开启fog - 调整可见度等参数即可
- 创建一个
volume
(Global or Box…),创建new一个Profile - 在场景中开启
open processing
,在Main Cemera
中Rendering
中启动post processing
- 设置Volume的参数,以下作为参考:
15.动画树Blend Tree
- 设置参数控制人物不同的动画
- 添加参数阈值以控制动画
在Update中实时调用切换动画:
public void SwitchAnimation(){//anim.SetFloat("Speed", agent.velocity.sqrMagnitude);anim.SetFloat("Speed", agent.velocity.magnitude);}
16.Shader Graph遮挡剔除
- 在Materials下建立一个 Shader-URP-Unlit Shader Graph,叫做 Occlusion Shader
- 在Occulsion下右键新建材质material
- 双击Occlusion Shader,打开以下面板:
修改URP通用渲染管线:
记得修改Player的图层,成功实现人物遮挡剔除效果
但此时树模型仍会遮挡到相机发出的射线。
解决办法:
方法一:
- 直接修改所有障碍物的图层为
Ignore Raycast
,即可忽略射线使玩家正常移动。
方法二:
- 取消tree的
Mesh Collider
组件,没有碰撞射线自然不会接触到障碍物了。
17.从0开始创建一个怪物
- 1.导入怪物的素材包
- 2.创建EnemyController脚本并通过脚本创建NavMesh Agent
- 3.设置Agent相关参数,比如碰撞体,速度等
- 4.通过枚举变量记录怪物的状态,并修改鼠标指针到攻击图标即可:
- 5.怪物脚本代码模板如下:
public enum EnemyStates { GUARD,PATROL,CHASE,DEAD}[RequireComponent(typeof(NavMeshAgent))]
public class EnemyController : MonoBehaviour
{//agent的控制器private NavMeshAgent agent;//怪物的状态(区分追逐怪和静止怪)public EnemyStates state;void Awake(){agent = GetComponent<NavMeshAgent>(); }void Update(){SwitchStates(); }void SwitchStates(){switch (state){case EnemyStates.GUARD:break;case EnemyStates.PATROL:break;case EnemyStates.CHASE:break;case EnemyStates.DEAD:break;}}
}
18.追踪攻击敌人
在MouseManager中:
//创建一个新的点击事件
public event Action<GameObject> OnEnemyClicked;//触发事件
void MouseControl(){//点击鼠标左键并且点击位置存在物体if (Input.GetMouseButtonDown(0) && hitInfo.collider != null){//点击到地面则操作挂载到人物上的事件if (hitInfo.collider.gameObject.CompareTag("Ground")){//判断事件是否为空,不为空则执行InvokeOnMouseClicked?.Invoke(hitInfo.point);}if (hitInfo.collider.gameObject.CompareTag("Enemy")){//判断事件是否为空,不为空则执行InvokeOnEnemyClicked?.Invoke(hitInfo.collider.gameObject);}}}
在PlayerController中:
//开始直接将点击事件注册到Start方法
//攻击目标
private GameObject attackTarget;
//攻击冷却时间
private float lastAttackTime;void Start(){MouseManager.Instance.OnMouseClicked += MoveToTarget;MouseManager.Instance.OnEnemyClicked += MoveToAttackTarget;}void Update()
{SwitchAnimation();//冷却衰减lastAttackTime -= Time.deltaTime;
}private void EventAttack(GameObject target){//判断攻击目标是否为空if (target != null){attackTarget = target;//调用协程方法StartCoroutine(MoveToAttackTarget());}}//写一个携程进行攻击检测IEnumerator MoveToAttackTarget(){agent.isStopped = false;transform.LookAt(attackTarget.transform);//如果两者距离大于攻击距离,则需要先跑动至攻击距离以内while (Vector3.Distance(attackTarget.transform.position,transform.position) > 1){agent.destination = attackTarget.transform.position;yield return null;}agent.isStopped = true;//Attackif (lastAttackTime < 0){anim.SetTrigger("Attack");//重置冷却时间lastAttackTime = 0.5f;}}
但此时攻击完无法移动,需要在移动行为函数中还原isStopped变量:
还需在攻击动画播放时,能打断攻击的行为:
解决以上两点问题,可修改移动行为如下:
public void MoveToTarget(Vector3 target){StopAllCoroutines();agent.isStopped = false;agent.destination = target;}
19.怪物追踪与镜头视角修改
- FreeLook摄像机的使用
怪物发现玩家代码逻辑:
[Header("Basic Settings")]
//发现范围半径
public float sightRadius;void SwitchStates(){//如果发现player 切换到CHASEif (FoundPlayer()){state = EnemyStates.CHASE;//Debug.Log("发现Player");}switch (state){case EnemyStates.GUARD:break;case EnemyStates.PATROL:break;case EnemyStates.CHASE:break;case EnemyStates.DEAD:break;}}bool FoundPlayer(){var colliders = Physics.OverlapSphere(transform.position, sightRadius);foreach (var target in colliders){if (target.CompareTag("Player")){return true;}}return false;}
20.怪物的状态切换逻辑
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;public enum EnemyStates { GUARD,PATROL,CHASE,DEAD}[RequireComponent(typeof(NavMeshAgent))]
public class EnemyController : MonoBehaviour
{//agent的控制器private NavMeshAgent agent;//怪物的状态private EnemyStates state;//动画控制器private Animator anim;[Header("Basic Settings")]public float sightRadius;//怪物是否为巡逻或静止public bool isGuard;private float speed;//获取玩家private GameObject attackTarget;//bool配合动画bool isWalk, isChase, isFollow;void Awake(){agent = GetComponent<NavMeshAgent>();speed = agent.speed;anim = GetComponent<Animator>();}void Update(){SwitchStates();SwitchAnimation();}void SwitchAnimation(){anim.SetBool("Walk", isWalk);anim.SetBool("Chase", isChase);anim.SetBool("Follow", isFollow);}void SwitchStates(){//如果发现player 切换到CHASEif (FoundPlayer()){state = EnemyStates.CHASE;//Debug.Log("发现Player");}switch (state){case EnemyStates.GUARD:break;case EnemyStates.PATROL:break;case EnemyStates.CHASE://TODO:追Player,如果超出范围则回到上一个状态//TODO:执行动画,在攻击范围内发起攻击isWalk = false;isChase = true;agent.speed = speed;if (!FoundPlayer()){isFollow = false;agent.destination = transform.position;}else{isFollow = true;agent.destination = attackTarget.transform.position;}break;case EnemyStates.DEAD:break;}}bool FoundPlayer(){var colliders = Physics.OverlapSphere(transform.position, sightRadius);foreach (var target in colliders){if (target.CompareTag("Player")){attackTarget = target.gameObject;return true;}}attackTarget = null;return false;}
}
21.怪物的随机巡逻点以及停留
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;public enum EnemyStates { GUARD,PATROL,CHASE,DEAD}[RequireComponent(typeof(NavMeshAgent))]
public class EnemyController : MonoBehaviour
{//agent的控制器private NavMeshAgent agent;//怪物的状态private EnemyStates state;//动画控制器private Animator anim;[Header("Basic Settings")]public float sightRadius;//怪物是否为巡逻或静止public bool isGuard;private float speed;//获取玩家private GameObject attackTarget;//巡逻停留时间public float lookAtTime;private float remainLookAtTime;//巡逻范围[Header("Patrol State")]public float patrolRange;private Vector3 wayPoint;private Vector3 guardPos;//bool配合动画bool isWalk, isChase, isFollow;void Awake(){agent = GetComponent<NavMeshAgent>();speed = agent.speed;anim = GetComponent<Animator>();guardPos = transform.position;remainLookAtTime = lookAtTime;}void Start(){if (isGuard){state = EnemyStates.GUARD;}else{state = EnemyStates.PATROL;GetNewWayPoint();}}void Update(){SwitchStates();SwitchAnimation();}void SwitchAnimation(){anim.SetBool("Walk", isWalk);anim.SetBool("Chase", isChase);anim.SetBool("Follow", isFollow);}void SwitchStates(){//如果发现player 切换到CHASEif (FoundPlayer()){state = EnemyStates.CHASE;//Debug.Log("发现Player");}switch (state){case EnemyStates.GUARD:break;case EnemyStates.PATROL:isChase = false;agent.speed = speed * 0.5f;if(Vector3.Distance(wayPoint,transform.position) <= agent.stoppingDistance){isWalk = false;if (remainLookAtTime > 0){remainLookAtTime -= Time.deltaTime;}else{GetNewWayPoint();}}else{isWalk = true;agent.destination = wayPoint;}break;case EnemyStates.CHASE://TODO:追Player,如果超出范围则回到上一个状态//TODO:执行动画,在攻击范围内发起攻击isWalk = false;isChase = true;agent.speed = speed;if (!FoundPlayer()){isFollow = false;if (remainLookAtTime > 0){agent.destination = transform.position;remainLookAtTime -= Time.deltaTime;}else if(isGuard){state = EnemyStates.GUARD;}else{state = EnemyStates.PATROL;}}else{isFollow = true;agent.destination = attackTarget.transform.position;}break;case EnemyStates.DEAD:break;}}bool FoundPlayer(){var colliders = Physics.OverlapSphere(transform.position, sightRadius);foreach (var target in colliders){if (target.CompareTag("Player")){attackTarget = target.gameObject;return true;}}attackTarget = null;return false;}void GetNewWayPoint(){remainLookAtTime = lookAtTime;float randomX = Random.Range(-patrolRange, patrolRange);float randomZ = Random.Range(-patrolRange, patrolRange);Vector3 randomPoint = new Vector3(guardPos.x + randomX, guardPos.y ,guardPos.z + randomZ);//碰撞体检测,防止怪物卡住NavMeshHit hit;//如果随机点不是Walkable,则会重新获取随机点wayPoint = NavMesh.SamplePosition(randomPoint, out hit, patrolRange, 1) ? hit.position : transform.position;}private void OnDrawGizmosSelected(){Gizmos.color = Color.blue;Gizmos.DrawWireSphere(transform.position, sightRadius);}}
22.使用ScriptableObject处理游戏数值
- 创建数值脚本
AttackData_SO
文件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;[CreateAssetMenu(fileName ="New Attack",menuName ="Attack/Attack Data")]
public class AttackData_SO : ScriptableObject
{//攻击距离public float attackRange;//远程攻击距离public float skillRange;//攻击冷却public float coolDown;//最小伤害阈值public int minDamage;//最大伤害阈值public int maxDamage;//暴击伤害public float criticalMultiplier;//暴击率public float criticalChance;
}
- 在Game Data下创建新的文件夹Attack Data,在其中创建数据集文件
Attack-->Attack Data
,并命名为Player BaseAttackData
。 - 在模板文件中设置数值:
- 写数据读取文件CharacterStats_SO:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class CharacterStats : MonoBehaviour
{public CharacterData_SO characterData;public AttackData_SO attackData;#region Read from Data_SOpublic int MaxHealth{get{return characterData != null ? characterData.maxHealth : 0;}set{characterData.maxHealth = value;}}public int CurrentHealth{get{return characterData != null ? characterData.currentHealth : 0;}set{characterData.currentHealth = value;}}public int BaseDefence{get{return characterData != null ? characterData.baseDefence : 0;}set{characterData.baseDefence = value;}}public int currentHealth{get{return characterData != null ? characterData.currentDefence : 0;}set{characterData.currentDefence = value;}}
}
#endregion
Unity3D一些项目经验相关推荐
- 面试Java后端开发的感受:如果就以平时项目经验来面试,通过估计很难——再论面试前的准备...
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:hsm_computer 链接:https://www.cn ...
- 面试季:如何在面试中介绍自己的项目经验
点击上方"方志朋",选择"设为星标" 做积极的人,而不是积极废人 来源:https://dwz.cn/2PrmlZCX 现在已经是7月份,一些互联网大厂已经开始 ...
- 没项目经验,如何砍下字节算法岗40万offer
又到了"金九银十"跳槽季,一大波大厂高薪岗位陆续开放,想拿Offer.升职加薪的你准备得怎么样了? 今年的招聘力度可以说是近几年最大的一次 ▲新浪科技报道 目前来看,算法岗薪资最高 ...
- 聊聊找工作中的项目经验问题(推荐系统和智能问答)
在求职过程中有这么一句话叫做"金九银十",也就是说,很多时候,求职的黄金时期就是在九月份和十月份,这两个月份中企业需求是最多的,求职的成功率也是最高的.但是随着AI方面的人才越来越 ...
- SFB 项目经验-65-使用域管理员安装不了Exchange 2010 SP3 CU21
问题: 1) 使用域管理员.企业管理员.架构管理员.组织管理员无法新建Exchange 2010的数据库 啥,这牛! 2) 使用域管理员.企业管理员.架构管理员.组织管理员无法安装Exchange 2 ...
- SFB 项目经验-13-为某上市企业仅安装Skype for Business 2016(图解)
****************************************************************************** <Skype for Busines ...
- SFB 项目经验-09-用Lync 2013或Skype for Business 2015抢火车票
本系列博文: Lync 项目经验-01-共存迁移-Lync2013-TO-SFB 2015-规划01 http://dynamic.blog.51cto.com/711418/1858520 Lync ...
- Linux就业技术指导(二):简历项目经验示例
一,期中项目经验示例 1.1 新服务器上线搭建系统环境 1,根据现有结构部署工具(PXE+kickstart) 2,结合应用系统需求定制部署模版 3,制作系统优化等一键执行脚本 4,自动化部署实施 5 ...
- 没有项目经验也能进大厂??
一年一度的秋招要开始了,又有人开始慌了. 前段时间在技术沙龙群里跟同学们聊天,大家集体吐槽今年求职内卷的严重. 因此,很多人陷入了一个死循环: 大厂校招需要岗位经历 → 我没有岗位经历,要找实习来获得 ...
最新文章
- linux服务器加固的命令,Linux 服务器安全加固
- JDK7,JDK8 - 下载地址
- 如何保证战略落地_战略如何规划落地?值得借鉴
- Django(part18)--静态文件
- bzoj3503: [Cqoi2014]和谐矩阵
- java 线程变量put_Java线程(篇外篇):线程本地变量ThreadLocal
- day11函数的进阶动态参数,命名空间,作用域,第一类对象
- 《移动通信》学习总结
- 进程之间信号收发并携带数据
- Learning Video Object Segmentation from Static Images
- select 获取option中其他的属性的值
- 【GlobalMapper精品教程】014:矢量线图层的创建及数字化操作
- Java 字符串拼接
- 通过网页获取图片操作步骤
- 企业估值研究到底从何处着手?
- 定义平行四边形类,继承四边形类,增加判断是否为平行四边形的函数
- 赛效:Xmind思维导图怎么删除子主题
- linux关键命令,Linux关键命令
- android wifi 框架图,android wifi框架
- 2020年最新WorldFirst注册图文教程(0.3%提现费)