子彈反射和曲線激光——用Unity還原東方彈幕(下)

作者:QXYO
前言
在上篇中只介紹了兩種彈幕樣式的生成,很明顯對于一款彈幕游戲來說是遠遠不夠的,所以本次就再來嘗試還原一下東方的兩種彈幕,反射子彈和曲線激光。
同樣的先來看看效果


本文主要內容:
控制子彈的反射
2. 曲線激光的生成
一、子彈在矩形范圍內反射
雖然標題是矩形,但同樣可以控制子彈是否只在單面反射。在Unity中可以用Vector3.Reflect
public static Vector3 Reflect(Vector3 inDirection, Vector3 inNormal)
來獲取到子彈與指定平面的反射角,并且有多方法來判斷子彈是否到了指定平面,碰撞檢測、射線、數(shù)學運算等方式。這里我就用數(shù)學方法來實現(xiàn)子彈反射。
首先定義矩形參數(shù)
? ? public float RectangleX = 0; //長方形的一個邊
? ? public float RectangleZ = 0; //長方形的一個邊
? ? public Transform CenterPos;? //圖形中心點
如果是未旋轉的矩形那么邊界平面就很好計算了,就是中心點位置+長或寬/2的位置,如果是旋轉過的矩形那么就需要點簡單的數(shù)學計算了,這里就不再展示。
? var disX = transform.position.x - CenterPos.position.x;
? var AbsdisX = disX > 0 ? disX : -disX;?
? //取x軸距離的絕對值,在數(shù)值不溢出的情況下比Mathf.Abs()效率高
? var disZ = transform.position.z - CenterPos.position.z;
? var AbsdisZ = disZ > 0 ? disZ : -disZ;
獲取到邊界后通過之前提到的 Vector3.Reflect方法就可以獲取到反射角,傳入的參數(shù)為方向也就是transform.forward和平面的法向量。
...
return Vector3.Reflect(transform.forward, disX > 0 ? -CenterPos.right : CenterPos.right);
...
return Vector3.Reflect(transform.forward, disZ > 0 ? -CenterPos.forward : CenterPos.forward);
...
如果中心點位置每幀移動速度過快,內部的子彈就會“逃出”反射范圍,但仍然會在原范圍內進行反射移動,所以可以添加判斷在范圍外的子彈不進行檢測。

下面給出完整代碼
? ? ? ? if (isRectangleRef)? //長方形檢測
? ? ? ? {
? ? ? ? ? ? var disX = transform.position.x - CenterPos.position.x;
? ? ? ? ? ? var AbsdisX = disX > 0 ? disX : -disX;? //取x軸距離的絕對值,在數(shù)值不溢出的情況下比Mathf.Abs()效率高
? ? ? ? ? ? var disZ = transform.position.z - CenterPos.position.z;
? ? ? ? ? ? var AbsdisZ = disZ > 0 ? disZ : -disZ;
? ? ? ? ? ? if (RectangleX !=0 && !isOut && AbsdisX > RectangleX)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? isOut = true;
? ? ? ? ? ? ? ? return Vector3.Reflect(transform.forward, disX > 0 ? -CenterPos.right : CenterPos.right);
? ? ? ? ? ? }
? ? ? ? ? ? if (RectangleZ != 0 && !isOut && AbsdisZ > RectangleZ)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? isOut = true;
? ? ? ? ? ? ? ? return Vector3.Reflect(transform.forward, disZ > 0 ? -CenterPos.forward : CenterPos.forward);
? ? ? ? ? ? }
? ? ? ? ? ? if (AbsdisX < RectangleX && AbsdisZ < RectangleZ)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? isOut = false;
? ? ? ? ? ? }
? ? ? ? }
二、子彈在圓形范圍內反射
原理和之前一樣,只不過要通過算距離來判斷是否到達邊界。需要參數(shù):距離圓心的半徑(radius)。注意檢測距離一般用距離的平方來判斷,減少開根號帶來的性能消耗。
var dis =(transform.position - CenterPos.position).sqrMagnitude;
到達邊界之后的法線向量其實就是該點朝向圓心的向量,即(CenterPos.position - transform.position)
? ? ? ? ? ? if (!isOut) //防止在圈外重復檢測
? ? ? ? ? ? {
? ? ? ? ? ? ? ? if (dis > Radius * Radius && dis < Radius * Radius + 4) //防止移動過快導致圓圈外的子彈的運動問題
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? isOut = true;
? ? ? ? ? ? ? ? ? ? return Vector3.Reflect(transform.forward, (CenterPos.position - transform.position).normalized);//法線向量就是當前位置朝向圓形半徑的向量
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? else
? ? ? ? ? ? {
? ? ? ? ? ? ? ? if (dis < Radius * Radius)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? isOut = false;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
來看下效果

三、曲線激光
形成直線激光子彈的方式有很多,特效粒子、長方形預制體等。但這些方法實現(xiàn)方便配置的曲線激光比較困難,所以本次使用的是LineRenderer組件。生成的曲線為正、余弦曲線。

本次主要用到的LineRenderer 屬性:
Positions: 設置線的開始點和結束點的位置。曲線就是生成了許多的位置信息并將其連接起來形成曲線。
Start Width:設置線開始時的寬度。
End Width:設置線結束時的寬度。
Use World Space:是否用世界坐標,可以根據需求判斷是否勾選,本方法取消了勾選。
同樣的先設置參數(shù)
? ? public float frequency;? //頻率
? ? public float amplitude;? //振幅
? ? public float lineDistance;? //相鄰兩個點的距離
? ? public float width;? ?//設置寬度
? ? public int lineCount; //曲線生成所需點個數(shù),越多越平滑
正、余弦曲線的偏移量(Y的值)可以很簡單的通過 Mathf.Cos()來獲取,曲線的移動有幾種方法:
1、通過開始就計算出Y軸的偏移量,使LineRenderer的每個Position的相對于自身旋轉的X軸不變,Y軸隨時間變化為計算的偏移量,因為取消勾選了Use World Space,所以可以通過修改自身的transform.position來實現(xiàn)移動。
? void GetCurve()? //獲取曲線坐標
? ? {
? ? ? ? for (int i = 0; i < lineCount; i++)
? ? ? ? {
? ? ? ? ? ? float t = frequency * Mathf.PI / lineCount * i;
? ? ? ? ? ? linePos[i] = new Vector3(disI * i, 1, Mathf.Cos(t) * amplitude);
? ? ? ? }
? ? }
2、LineRenderer的前一個點的位置是后一個點下一次移動到的位置,這樣每幀修改第一個點的位置即可。為了防止激光突兀的全部顯示,點位要逐個生成。本次生成和移動的方式是通過協(xié)程來實現(xiàn)。
? ?float GetPosY(float x) //獲取Y軸偏移量
? ? {
? ? ? ? return Mathf.Cos(frequency * x * Mathf.PI) * amplitude;
? ? }
...
? ?IEnumerator Move()
? ? {
? ? ? ? if (CreatCount == lineCount)? //判斷點位是否全部生成完畢
? ? ? ? {
? ? ? ? ? ? linePos = new Vector3[lineRenderer.positionCount];
? ? ? ? ? ? lineRenderer.GetPositions(linePos); // 重新獲取當前點位數(shù)據
? ? ? ? }
? ? ? ? var offset = new Vector3(GetPosY(lineDistance / lineCount * disI), 1, lineDistance / lineCount * disI);
? ? ? ?//獲取本次移動后第一個點的位置
? ? ? ? disI++;
? ? ? ? if (CreatCount < lineCount)? //逐步生成點位
? ? ? ? {
? ? ? ? ? ? CreatCount++;
? ? ? ? ? ? lineRenderer.positionCount = CreatCount;
? ? ? ? }
? ? ? ? lineRenderer.SetPosition(CreatCount - 1, offset);
? ? ? ? linePos[CreatCount - 1] = offset;
? ? ? ? for (int i = CreatCount - 2; i >= 0; i--)? //跟隨前一個點位
? ? ? ? {
? ? ? ? ? ? lineRenderer.SetPosition(i, linePos[i + 1]);
? ? ? ? }
? ? ? ? yield return new WaitForSeconds(speed * Time.fixedDeltaTime);
? ? }

以上都是在3D場景下的彈幕,大家有興趣也可以修改成3D樣式。
由于還原東方彈幕所需要大量時間進行微調參數(shù),所以本次就只將一些基礎彈幕樣式展現(xiàn)出來供大家參考,最后放上工程鏈接,建議使用2019以上的版本打開:
地址:https://pan.baidu.com/share/init?surl=WbHvaViGDLlwBTP3hIwziA
提取碼:vf43

對游戲開發(fā)學習感興趣的盆友,歡迎訪問:http://levelpp.com/
同時,也歡迎加入游戲開發(fā)群攪基:610475807