8.彈幕系統算法
彈幕系統的設計體現了射擊遊戲的基本要素,玩家要在敵人放出的大量子彈(彈幕)的細小空隙間閃避,能在玩家閃躲彈幕的時候給玩家帶來快感,接近滿屏的子彈,增長了對玩家的視覺衝擊力。網站
每個敵機都持有一個彈幕實例,每一個彈幕實例中包含多個子彈實例,經過配置彈幕的屬性,使每一個子彈實例在軌跡管理器的做用下,造成一種有規律性的直線運動,在視覺上給玩家展示出彈幕的效果。如圖8-1所示。ui
步驟1:spa
子彈類,定義子彈的屬性和藉口。設計
01 |
public class Bullet |
02 |
{ |
03 |
//從模板建立子彈Actor |
04 |
public void CreateActor () |
05 |
{ |
06 |
_obj = ActorManager.CreateFromTemplate( @"asset:bullet01.template" , false ); |
07 |
08 |
} |
09 |
10 |
//激活,顯示子彈 |
11 |
public void ActiveObj (Vector2 startPos) |
12 |
{ |
13 |
_obj.Active(); |
14 |
if (_obj.GetChildCount()>0) |
15 |
{ |
16 |
_obj.GetChild(0).Active(); |
17 |
} |
18 |
19 |
_obj.WorldPosition = new Vector3 (startPos.X,startPos.Y,2.0f); |
20 |
} |
21 |
22 |
//隱藏子彈 |
23 |
public void DeactiveObj () |
24 |
{ |
25 |
_obj.Deactive(); |
26 |
if (_obj.GetChildCount()>0) |
27 |
{ |
28 |
_obj.GetChild(0).Deactive(); |
29 |
} |
30 |
} |
31 |
private Actor _obj = new Actor(); |
32 |
private UInt32 _id; |
33 |
private UInt32 _target_id; |
34 |
private Vector2 _startPos; |
35 |
private bool _isShooted = false ; |
36 |
} |
步驟2:3d
配置彈幕發射子彈的屬性。code
01 |
public class Barrage |
02 |
{ |
03 |
//發射子彈 |
04 |
public void ShootBullet ( float elapsedTime, Vector3 pos) |
05 |
{ |
06 |
timer += elapsedTime; |
07 |
if (timer >= _shoot_interval && _needshoot_id <= (Bullets.Count - 1)) |
08 |
{ |
09 |
Vector2 posV2 = new Vector2(pos.X,pos.Y); |
10 |
Bullets[_needshoot_id].ActiveObj(posV2); |
11 |
Bullets[_needshoot_id].SetShooted( true ); |
12 |
_needshoot_id ++; |
13 |
timer = 0.0f; |
14 |
} |
15 |
private UInt32 _id; |
16 |
private UInt32 _obj_id; |
17 |
private float _start_speed; |
18 |
private float _accel_speed; |
19 |
private float _shoot_interval; |
20 |
private float _shoot_direction; |
21 |
private float _direction_offset; |
22 |
private List< Bullet> Bullets; |
23 |
private TrajectoryType _tt ; |
24 |
private float timer = 0.0f; |
25 |
private int _needshoot_id = 0; |
26 |
private Actor _owner; |
27 |
} |
28 |
} |
步驟3:遊戲
設計彈幕管理器,管理每個彈幕實例的發射。遊戲開發
01 |
public class BarrageMgr |
02 |
{ |
03 |
//請求子彈 |
04 |
public bool AskForBullets ( int count, List< Bullet> bullets, Actor owner) |
05 |
{ |
06 |
if (ReloadBullet.Count == 0) |
07 |
{ |
08 |
return false ; |
09 |
} |
10 |
11 |
if (count >= ReloadBullet.Count) |
12 |
{ |
13 |
count = ReloadBullet.Count; |
14 |
} |
15 |
16 |
for ( int i = 0; i < count; i++) |
17 |
{ |
18 |
ReloadBullet[i].DeactiveObj(); |
19 |
Vector2 pos = new Vector2(owner.WorldPosition.X,owner.WorldPosition.Y); |
20 |
21 |
ReloadBullet[i].setPos(pos); |
22 |
bullets.Add(ReloadBullet[i]); |
23 |
24 |
} |
25 |
ReloadBullet.RemoveRange(0,count); |
26 |
return true ; |
27 |
} |
28 |
//處理軌跡 |
29 |
public void DealTrajectory (Barrage barrage, float elapsedTime) |
30 |
{ |
31 |
_trajectoryMgr.MoveBarrage(barrage,elapsedTime); |
32 |
} |
33 |
//更新彈幕位置 |
34 |
public void Tick( float elapsedTime) |
35 |
{ |
36 |
foreach (KeyValuePair< uint ,Barrage> pair in _barrageDict) |
37 |
{ |
38 |
39 |
Barrage barrage = pair.Value; |
40 |
|
41 |
DealTrajectory(barrage,elapsedTime); |
42 |
barrage.DestroyBullet(); |
43 |
if (!barrage.IsOwnerActive() && barrage.IsAllBulletsDeactive()) |
44 |
{ |
45 |
barrage.Reload(); |
46 |
} |
47 |
} |
48 |
Debug.Dbgout( _barrageDict.Count.ToString() ); |
49 |
} |
50 |
51 |
} |
步驟4:開發
設計軌跡管理器,使子彈造成一種有規律性的直線運動。
01 |
public class Trajectory |
02 |
{ |
03 |
//直線軌跡算法 |
04 |
public static Vector2 GoStraight(Vector2 start_pos, float direction, |
05 |
float start_speed, float accel_speed, float use_time, out Vector2 pos) |
06 |
{ |
07 |
float angle = direction * ( float )Math.PI / 180.0f; |
08 |
float seconds = ( float )use_time; |
09 |
float move_length = start_speed * seconds + accel_speed * seconds * seconds / 2.0f; |
10 |
pos.X = move_length * ( float )Math.Cos(angle) + start_pos.X; |
11 |
pos.Y = move_length * ( float )Math.Sin(angle) + start_pos.Y; |
12 |
return pos; |
13 |
} |
14 |
|
15 |
//這裏的跟蹤算法主要適用於勻速圓周運動類型的要跟蹤,速率不變,必定的旋轉角度 |
16 |
//追蹤軌跡算法 |
17 |
public static Vector2 Tracking(Vector2 start_pos, ref float direction, Vector2 dest_pos, |
18 |
float start_speed, float accel_speed, float track_degree, float use_time) |
19 |
{ |
20 |
Vector2 newpos = new Vector2(0, 0); |
21 |
|
22 |
if (direction < 0) |
23 |
{ |
24 |
direction += 360; |
25 |
} |
26 |
else |
27 |
{ |
28 |
direction = direction % 360; |
29 |
} |
30 |
|
31 |
//判斷目標與飛行的夾角 |
32 |
float degree =( float ) (Math.Atan2(dest_pos.Y - start_pos.Y, |
33 |
dest_pos.X - start_pos.X) * 180 / Math.PI); |
34 |
if (degree < 0) |
35 |
{ |
36 |
degree += 360; |
37 |
} |
38 |
|
39 |
//最小目標夾角 |
40 |
float dest_degree = ( float )Math.Abs(degree - direction) % 360; |
41 |
if (dest_degree > 180) |
42 |
{ |
43 |
dest_degree = 360 - dest_degree; |
44 |
} |
45 |
if (dest_degree < 0.000001) |
46 |
{ |
47 |
GoStraight(start_pos, direction, start_speed, accel_speed, use_time, out newpos); |
48 |
return newpos; |
49 |
} |
50 |
|
51 |
//計算最終旋轉的夾角 |
52 |
float use_seconds = use_time / 1000.0f; |
53 |
float rotate_degree = track_degree * use_seconds; |
54 |
if (rotate_degree > dest_degree) |
55 |
{ |
56 |
rotate_degree = dest_degree; |
57 |
} |
58 |
double inner_degree = degree - direction; |
59 |
if (inner_degree > 180) |
60 |
{ |
61 |
direction -= rotate_degree; |
62 |
} |
63 |
else if (inner_degree <= 180 && inner_degree >= 0) |
64 |
{ |
65 |
direction += rotate_degree; |
66 |
} |
67 |
else if (inner_degree < -180) |
68 |
{ |
69 |
direction += rotate_degree; |