From a974149c9e49a220ae1dd074e34253f0afad803b Mon Sep 17 00:00:00 2001 From: copygirl Date: Thu, 13 May 2021 19:00:35 +0200 Subject: [PATCH] Add "ghost" bullets & networking --- scene/Player.tscn | 16 ++++++--- src/Items/Weapon.cs | 45 +++++++++++++++++++++--- src/Objects/Bullet.cs | 72 +++++++++++++++++++++++++++++++++++++++ src/Utility/Extensions.cs | 14 ++++++++ 4 files changed, 137 insertions(+), 10 deletions(-) create mode 100644 src/Objects/Bullet.cs diff --git a/scene/Player.tscn b/scene/Player.tscn index e0432a7..cd98cb9 100644 --- a/scene/Player.tscn +++ b/scene/Player.tscn @@ -68,10 +68,11 @@ texture = ExtResource( 7 ) offset = Vector2( 8, 0 ) script = ExtResource( 8 ) Knockback = 50.0 -Spread = 3.0 +Spread = 1.5 SpreadIncrease = 1.0 RecoilMin = 3.0 RecoilMax = 5.0 +BulletSpeed = 1200 [node name="Tip" type="Node2D" parent="Items/Revolver"] position = Vector2( 15, -2.5 ) @@ -87,10 +88,13 @@ script = ExtResource( 8 ) EffectiveRange = 240 MaximumRange = 360 Knockback = 135.0 -Spread = 14.0 -SpreadIncrease = 20.0 +Spread = 8.0 +SpreadIncrease = 10.0 RecoilMin = 6.0 RecoilMax = 12.0 +BulletSpeed = 1000 +BulletsPetShot = 8 +BulletOpacity = 0.1 [node name="Tip" type="Node2D" parent="Items/Shotgun"] position = Vector2( 22, -1.5 ) @@ -109,6 +113,8 @@ Knockback = 100.0 SpreadIncrease = 2.0 RecoilMin = 8.0 RecoilMax = 8.0 +BulletSpeed = 4000 +BulletOpacity = 0.4 [node name="Tip" type="Node2D" parent="Items/Rifle"] position = Vector2( 24, -1.5 ) @@ -122,8 +128,8 @@ texture = ExtResource( 12 ) offset = Vector2( 8, 0 ) script = ExtResource( 8 ) Knockback = 30.0 -Spread = 1.0 -SpreadIncrease = 1.2 +Spread = 0.6 +SpreadIncrease = 0.8 RecoilMin = 1.0 RecoilMax = 2.5 diff --git a/src/Items/Weapon.cs b/src/Items/Weapon.cs index 8900ed9..d5af8e6 100644 --- a/src/Items/Weapon.cs +++ b/src/Items/Weapon.cs @@ -13,6 +13,10 @@ public class Weapon : Sprite [Export] public float RecoilMin { get; set; } = 0.0F; [Export] public float RecoilMax { get; set; } = 0.0F; + [Export] public int BulletSpeed { get; set; } = 2000; + [Export] public int BulletsPetShot { get; set; } = 1; + [Export] public float BulletOpacity { get; set; } = 0.2F; + public Cursor Cursor { get; private set; } public Player Player { get; private set; } @@ -34,14 +38,45 @@ public class Weapon : Sprite if (!(Player is LocalPlayer localPlayer)) return; if (ev.IsActionPressed("interact_primary")) { + var seed = unchecked((int)GD.Randi()); + ShootInternal(AimDirection, Scale.y > 0, seed); + RpcId(1, nameof(Shoot), AimDirection, Scale.y > 0, seed); + localPlayer.Velocity -= Mathf.Polar2Cartesian(Knockback, Rotation); + } + } + + [Remote] + private void Shoot(float aimDirection, bool toRight, int seed) + { + if (this.GetGame() is Server) { + if (Player.NetworkID != GetTree().GetRpcSenderId()) return; + // TODO: Verify input. + Rpc(nameof(Shoot), aimDirection, toRight, seed); + } else if (Player is LocalPlayer) return; + ShootInternal(aimDirection, toRight, seed); + } + private void ShootInternal(float aimDirection, bool toRight, int seed) + { + if (this.GetGame() is Client) GetNodeOrNull("Fire")?.Play(); - // TODO: Spawn bullet or something. - // TODO: Tell server (and other clients) we shot. - _currentSpreadInc += Mathf.Deg2Rad(SpreadIncrease); - _currentRecoil += Mathf.Deg2Rad((float)GD.RandRange(RecoilMin, RecoilMax)); - localPlayer.Velocity -= Mathf.Polar2Cartesian(Knockback, Rotation); + var random = new Random(seed); + var angle = aimDirection - _currentRecoil * (toRight ? 1 : -1); + + var tip = GetNode("Tip").Position; + if (!toRight) tip.y *= -1; + tip = tip.Rotated(angle); + + for (var i = 0; i < BulletsPetShot; i++) { + var spread = (Mathf.Deg2Rad(Spread) + _currentSpreadInc) * Mathf.Clamp(random.NextGaussian(0.4F), -1, 1); + var dir = Mathf.Polar2Cartesian(1, angle + spread); + var color = new Color(Player.Color, BulletOpacity); + var bullet = new Bullet(Player.Position + tip, dir, EffectiveRange, MaximumRange, BulletSpeed, color); + this.GetWorld().AddChild(bullet); } + + _currentSpreadInc += Mathf.Deg2Rad(SpreadIncrease); + _currentRecoil += Mathf.Deg2Rad(random.NextFloat(RecoilMin, RecoilMax)); } public override void _Process(float delta) diff --git a/src/Objects/Bullet.cs b/src/Objects/Bullet.cs new file mode 100644 index 0000000..48ef056 --- /dev/null +++ b/src/Objects/Bullet.cs @@ -0,0 +1,72 @@ +using System; +using Godot; + +public class Bullet : Node2D +{ + private static readonly TimeSpan TRAIL_DURATION = TimeSpan.FromSeconds(0.6); + + public Vector2 Direction { get; } + public int EffectiveRange { get; } + public int MaximumRange { get; } + public int Speed { get; } + public Color Color { get; } + + private readonly Vector2 _startPosition; + private TimeSpan _age; + private float _distance; + + public Bullet(Vector2 position, Vector2 direction, + int effectiveRange, int maximumRange, int speed, Color color) + { + _startPosition = Position = position; + Direction = direction; + EffectiveRange = effectiveRange; + MaximumRange = maximumRange; + Speed = speed; + Color = color; + } + + public override void _Ready() + { if (this.GetGame() is Server) Visible = false; } + + public override void _Process(float delta) + { + _age += TimeSpan.FromSeconds(delta); + + if (_distance < MaximumRange) { + _distance = Mathf.Min(MaximumRange, Speed * (float)_age.TotalSeconds); + Position = _startPosition + Direction * _distance; + Update(); + } + + if (_age > TRAIL_DURATION) { + Modulate = new Color(Modulate, Modulate.a - delta * 2); + if (Modulate.a <= 0) this.RemoveFromParent(); + } + } + + public override void _Draw() + { + var numPoints = 2 + + ((_distance > 16) ? 1 : 0) + + ((_distance > EffectiveRange) ? 1 : 0); + var points = new Vector2[numPoints]; + var colors = new Color[numPoints]; + + if (_distance > 16) + colors[0] = new Color(Color, Color.a * Mathf.Min(1.0F, 1.0F - (_distance - EffectiveRange) / (MaximumRange - EffectiveRange))); + + if (_distance > EffectiveRange) { + points[1] = Direction * -(_distance - EffectiveRange); + colors[1] = Color; + } + + points[points.Length - 2] = Direction * -Mathf.Max(0.0F, _distance - 16); + points[points.Length - 1] = Direction * -_distance; + + colors[colors.Length - 2] = Color; + colors[colors.Length - 1] = new Color(Color, 0.0F); + + DrawPolylineColors(points, colors, 1.5F, true); + } +} diff --git a/src/Utility/Extensions.cs b/src/Utility/Extensions.cs index c15d3a5..a6a6363 100644 --- a/src/Utility/Extensions.cs +++ b/src/Utility/Extensions.cs @@ -1,3 +1,4 @@ +using System; using Godot; public static class Extensions @@ -24,6 +25,19 @@ public static class Extensions node.GetParent().RemoveChild(node); node.QueueFree(); } + + public static float NextFloat(this Random random) + => (float)random.NextDouble(); + public static float NextFloat(this Random random, float min, float max) + => min + NextFloat(random) * (max - min); + + public static float NextGaussian(this Random random, float stdDev = 1.0F, float mean = 0.0F) + { + var u1 = 1.0F - random.NextFloat(); + var u2 = 1.0F - random.NextFloat(); + var normal = Mathf.Sqrt(-2.0F * Mathf.Log(u1)) * Mathf.Sin(2.0F * Mathf.Pi * u2); + return mean + stdDev * normal; + } } public interface IInitializable