演示视频地址:
先放gif,等我有时间在bilibili出讲解视频
能量球shader参考文档:
Shader 能量法球特效_unity 用纹理实现球型能量能量进度效果-CSDN博客
开发流程(面向对象):
1.交互方式,常见的就是,手柄扳机键长按蓄能,随着时间,能量球放大到一定大小,停止放大,松开扳机键发射能量弹。
2.确定能量球的特效效果,碰撞特效,音效。
3.因为蓄能发射的关系,所以代码实现子弹单次发射的逻辑就好,常用的对象池实现方式,碰撞到物体后实例化碰撞的粒子特效。碰撞后是否销毁物体,还是让物体失活。
4.用协程改变能量球大小
下面是hierarchy的物体关系图
没啥注意的,记得吧子弹预制体里的刚体设置为,CxxxxDynamic,碰撞有关的物体记得加上刚体和碰撞体(两者都要)。
下面贴出关键代码:
1.子弹预制体实例化(对象池)的代码:
public AudioSource audio;
private GameObject bj1;
private GameObject bj2;
private GameObject bj3;
private GameObject bj4;
private ZHZUtil util;
public static BulletIncubator instance;
public int amountToPool;
public GameObject m_bullet;
public List<GameObject> m_pooledObjectlist;
private void Awake()
{
instance = this;
}
void Start()
{
GameObject[] siblings = FindObjectsOfType<GameObject>();
foreach (GameObject child in siblings)
{
if (child.name == "部件1")
{
bj1 = child;
}
if (child.name == "部件2")
{
bj2 = child;
}
if (child.name == "部件3")
{
bj3 = child;
}
if (child.name == "部件4")
{
bj4 = child;
}
}
//实例化子弹
m_pooledObjectlist = new List<GameObject>();
GameObject tmp;
for (int i = 0; i < amountToPool; i++)
{
tmp = Instantiate(m_bullet, transform);
tmp.SetActive(false);
m_pooledObjectlist.Add(tmp);
}
}
/// <summary>
/// 获取可用的子弹
/// </summary>
/// <returns></returns>
public GameObject GetPooledObject()
{
if (m_pooledObjectlist.Count != 0)
{
for (int i = 0; i < m_pooledObjectlist.Count; i++)
{
if (!m_pooledObjectlist[i].activeInHierarchy)
{
GameObject zidan = m_pooledObjectlist[i];
m_pooledObjectlist.RemoveAt(i);
return zidan;
}
}
}
else
{
if (bj1.active || bj2.active || bj3.active || bj4.active)
{
var newpop = ZHZVRKit.PopUp("很抱歉你挑战失败了,还是去学习吧!", duration: 99f);
Debug.Log("结束");
audio.Play();
Invoke("goanohthoerscene", 5f);
return null;
}
}
return null;
}
public void goanohthoerscene()
{
SceneManager.LoadScene(1);
}
2.子弹生成与发射时的逻辑代码:
[Header("发射子弹")]
private GameObject go;
public float bulletspeed;
public XRController rController;
private BulletIncubator m_BulletIncubator;
private ZHZUtil util;
private float shootingtime = 0.5f;
private bool isfire = false;
[Header("改变子弹的大小")]
private float targetSize = 10f; // 目标大小
public float changeDuration = 2f; // 变化持续时间
private Vector3 initialSize; // 初始大小
private Vector3 targetScale; // 目标缩放值
private float elapsedTime = 0f; // 已过去的时间
private bool isbuilt = true;
[Header("音效")]
public AudioSource juneng;
public AudioSource fashe;
private Coroutine changesize_zidan=null;
private float t=0;
//public Texture2D MyCursor;
// Start is called before the first frame update
private void Awake()
{
util = ZHZUtil.Instance;
util.ResetInstance();
}
void Start()
{
m_BulletIncubator = BulletIncubator.instance;
}
// Update is called once per frame
void Update()
{
/* if (Input.GetMouseButton(0))
{
elapsedTime += Time.deltaTime;
Debug.Log("built");
built_zi_dan();
Debug.Log(t);
}
if (Input.GetMouseButtonUp(0) && isfire)
{
go.transform.parent = null;
StopCoroutine(change_zidan_size());
isbuilt = true;
biubiu();
}*/
if (rController.inputDevice.TryGetFeatureValue(CommonUsages.trigger, out float trigger) && trigger > 0.5f)
{
elapsedTime += Time.deltaTime;
built_zi_dan();
}
if (rController.inputDevice.TryGetFeatureValue(CommonUsages.trigger, out float trigger1) && trigger1 == 0f && isfire)
{
elapsedTime = 0f;
go.transform.parent = null;
StopCoroutine(change_zidan_size());
isbuilt = true;
biubiu();
}
}
private void biubiu()
{
Ray ray = new Ray(rController.transform.position, rController.transform.forward);
if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity) && isfire)
{
juneng.Stop();
fashe.Play();
Debug.Log("biu");
go.GetComponent<Rigidbody>().velocity = rController.transform.forward * bulletspeed;
isfire = false;
isbuilt = true;
}
}
private void built_zi_dan() {
/*if (rController.inputDevice.TryGetFeatureValue(CommonUsages.trigger, out float trigger)){*/
if (isbuilt)
{
elapsedTime = 0f;
t = 0f;
go = m_BulletIncubator.GetPooledObject();
isbuilt = false;
if (go != null)
{
juneng.Play();
go.transform.position = transform.Find("gGUN03h").Find("startpos").position;
go.SetActive(true);
go.transform.parent = rController.transform;
initialSize = go.transform.localScale;
targetScale = initialSize * targetSize;
changesize_zidan = StartCoroutine(change_zidan_size());
isfire = true;
}
}
/*}*/
}
private IEnumerator change_zidan_size() {
while (elapsedTime < changeDuration)
{
Debug.Log("t=" + t);
t = elapsedTime / changeDuration;
if (go != null)
{
go.transform.localScale = Vector3.Lerp(initialSize, targetScale, t);
}
yield return null;
}
if (go != null)
{
go.transform.localScale = targetScale;
}
}
木箱往返运动代码:
/// <summary>
/// 物体和方位信息
/// 各个部件,在爆炸前和爆炸后的位置和方向信息
/// </summary>
[Serializable]
public class goPositions
{
[Header("物体")]
[SerializeField]
public GameObject go;
[Header("爆炸前的位置")]
[SerializeField]
public Vector3 positionStart;
[Header("爆炸前的方向")]
[SerializeField]
public Quaternion rotationStart;
[Header("爆炸后的位置")]
[SerializeField]
public Vector3 positionEnd;
[Header("爆炸后的方向")]
[SerializeField]
public Quaternion rotationEnd;
}
[Header("部件的根物体")]
[SerializeField]
public GameObject root;
[Header("部件以及方位信息列表")]
[SerializeField]
public List<goPositions> GoPositions = new List<goPositions>();
[Header("散开的时间")]
[SerializeField]
public float time;
/// <summary>
/// pingpong效果的进度条值
/// </summary>
private float stepValue;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
//更新物体的位置和方向——pingpong效果
stepValue = Mathf.PingPong(Time.time, time) / time;
GoPositions.ForEach(x =>
{
x.go.transform.position = Vector3.Lerp(x.positionStart, x.positionEnd, stepValue);
x.go.transform.rotation = Quaternion.Lerp(x.rotationStart, x.rotationEnd, stepValue);
});
}
/// <summary>
/// 物体摆放到初始位置
/// </summary>
void SetToStartTransform()
{
GoPositions.ForEach(
x =>
{
x.go.transform.position = x.positionStart;
x.go.transform.rotation = x.rotationStart;
});
}
void SetToEndTransform()
{
GoPositions.ForEach(
x =>
{
x.go.transform.position = x.positionEnd;
x.go.transform.rotation = x.rotationEnd;
});
}
#if UNITY_EDITOR // Editor的非running状态使用
[ContextMenu("记录【部件和开始方位】")]
#endif
public void InitTransformStart()
{
GoPositions.Clear();
root.transform.GetComponentInChildren<Transform>().Cast<Transform>().ToList()
.ForEach(item =>
{
if (item.name.Contains("部件")) //
{
Debug.Log(item.name);
goPositions gp = new goPositions();
gp.go = item.gameObject;
gp.positionStart = item.gameObject.transform.position;
gp.rotationStart = item.gameObject.transform.rotation;
GoPositions.Add(gp);
}
else
{
//
}
});
Debug.Log("初始方位记录完毕");
}
#if UNITY_EDITOR // Editor的非running状态使用
[ContextMenu("记录【结束方位】")]
#endif
public void InitTransformEnd()
{
var allGos = root.transform.GetComponentInChildren<Transform>().Cast<Transform>().ToList();
GoPositions.ForEach(x =>
{
x.positionEnd = allGos.Where(go => go.gameObject.name == x.go.name).ToList()[0].position;
x.rotationEnd = allGos.Where(go => go.gameObject.name == x.go.name).ToList()[0].rotation;
});
Debug.Log("结束方位记录完毕");
}
子弹本身的逻辑代码:
public ParticleSystem boompt;
private GameObject bj1;
private GameObject bj2;
private GameObject bj3;
private GameObject bj4;
private static int count=0;
void Start()
{
GameObject[] siblings = FindObjectsOfType<GameObject>();
foreach (GameObject child in siblings)
{
if (child.name == "部件1")
{
bj1 = child;
}
if (child.name == "部件2")
{
bj2 = child;
}
if (child.name == "部件3")
{
bj3 = child;
}
if (child.name == "部件4")
{
bj4 = child;
}
}
}
// Update is called once per frame
void Update()
{
}
private void OnTriggerEnter(Collider other)
{
Rigidbody rb = other.GetComponent<Rigidbody>();
if (rb != null)
{
Debug.Log(other.name);
// 获取碰撞位置
Vector3 collisionPos = other.ClosestPoint(transform.position);
// 实例化爆炸特效
Instantiate(boompt, collisionPos, Quaternion.identity);
if (other.name == "部件1") {bj1.SetActive(false);count+=1;}
if (other.name == "部件2") {bj2.SetActive(false);count+= 1; }
Debug.Log(count);
if (other.name == "部件3"){ bj3.SetActive(false);count+=1; }
if (other.name == "部件4") {bj4.SetActive(false);count+=1 ; }
if (count == 4)
{
SceneManager.LoadScene(1);
}
Destroy(transform.gameObject);
Debug.Log("打爆了" + count);
}
}