Shooting and big tank fix

This commit is contained in:
Marco 2025-06-20 15:39:19 +02:00
commit 0a6e89faed
26 changed files with 797 additions and 9 deletions

View file

@ -0,0 +1,55 @@
using Cirno.Scripts.Components;
using Cirno.Scripts.Controllers;
using Cirno.Scripts.Weapons;
using Godot;
namespace Cirno.Scripts.Actors;
public partial class BulletSpawner3D : Node3D
{
[Export] public PackedScene BulletScene;
public void SpawnBullet(BulletInfo bulletInfo, Vector3 position)
{
var bulletScene = bulletInfo.BulletScene ?? BulletScene;
Bullet3D bullet;
int count = bulletInfo.BulletCount;
float spreadRadians = Mathf.DegToRad(bulletInfo.Spread);
float step = count > 1 ? spreadRadians / (count - 1) : 0;
for (int i = 0; i < count; i++)
{
bullet = PoolingManager.Instance.SpawnBullet<Bullet3D>(bulletInfo.OriginalBulletResource);
bullet.GlobalPosition = position;
if (bulletInfo.Modifier is not null)
{
bulletInfo = bulletInfo.Modifier.ModifyBullet(bulletInfo, i, count);
}
bullet.Initialize(bulletInfo);
Vector2 baseDirection = bulletInfo.Direction == Vector2.Zero ? Vector2.Right : bulletInfo.Direction.Normalized();
float baseAngle = Mathf.Atan2(baseDirection.Y, baseDirection.X);
// Spread centered: offset from center
float offsetFromCenter = (i - (count - 1) / 2.0f) * step;
float angle = baseAngle + Mathf.DegToRad(bulletInfo.RotationOffset) + offsetFromCenter;
Vector2 bulletDirection = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));
bullet.SetDirection(bulletDirection);
// float offsetRadians = Mathf.DegToRad(bulletInfo.RotationOffset);
// float spreadStep = Mathf.DegToRad(bulletInfo.Spread) / Mathf.Max(1, bulletInfo.BulletCount - 1); // Ensure proper spread spacing, also add 1 if 0
// float angle = baseAngle + offsetRadians + (spreadStep * i);
//
//
// Vector2 bulletDirection = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));
//
// bullet.SetDirection(bulletDirection);
}
}
}

View file

@ -0,0 +1 @@
uid://bf5f2phitdqw8

View file

@ -0,0 +1,97 @@
using Cirno.Scripts.AttackPatterns;
using Cirno.Scripts.Resources;
using Cirno.Scripts.Resources.BulletScripts;
using Godot;
namespace Cirno.Scripts.Actors;
public partial class ScriptableBulletsEmitter3D : Node3D, IActivable, IScriptHost3D
{
public Node3D ParentObject => this;
[Export]
public BulletScript3D Script { get; set; }
[Export]
public bool InvertSignal { get; private set; } = false;
[Export]
public bool EmitOnStart { get; set; } = false;
[Signal]
public delegate void StateChangedEventHandler(bool isEmitting);
private bool _isActive = false;
//private BulletScript _scriptInstance;
protected BulletScript3D.BulletScriptMachine ScriptMachine;
public override void _Ready()
{
//_scriptInstance = Script.Duplicate(true) as BulletScript;
ScriptMachine = Script.Make(this);
_homePosition = this.GlobalPosition;
if (EmitOnStart)
{
_isActive = true;
ScriptMachine.Start();
}
EmitSignal(SignalName.StateChanged, _isActive);
}
public bool Activate(ActivationType activationType = ActivationType.Toggle)
{
switch (activationType)
{
case ActivationType.Use:
case ActivationType.Toggle:
_isActive = !_isActive;
break;
case ActivationType.Open:
case ActivationType.Enable:
_isActive = !InvertSignal;
break;
case ActivationType.Close:
case ActivationType.Disable:
_isActive = InvertSignal;
break;
case ActivationType.Destroy:
_isActive = InvertSignal;
// TODO: Explode
break;
}
if (_isActive)
{
ScriptMachine.Start();
}
EmitSignal(SignalName.StateChanged, _isActive);
return true;
}
public void Toggle()
{
this.Activate();
}
public override void _Process(double delta)
{
if (!_isActive) return;
ScriptMachine.UpdatePhase(delta);
}
private Vector3 _homePosition;
public Vector3 HomePosition => _homePosition;
public void ChangeSpriteDirection(Vector2 direction)
{
}
}

View file

@ -0,0 +1 @@
uid://dpibtd73awwp7

View file

@ -0,0 +1,10 @@
using Godot;
namespace Cirno.Scripts.AttackPatterns;
public interface IScriptHost3D
{
public Node3D ParentObject { get; }
public Vector3 HomePosition { get; }
public void ChangeSpriteDirection(Vector2 direction);
}

View file

@ -0,0 +1 @@
uid://c4mqs0ovhgk0x

View file

@ -0,0 +1,228 @@
using Cirno.Scripts.Actors;
using Cirno.Scripts.Components;
using Cirno.Scripts.Resources;
using Cirno.Scripts.Utils;
using Godot;
using Godot.Collections;
namespace Cirno.Scripts.AttackPatterns;
[GlobalClass]
[Tool]
public partial class ShootingPattern3D : AttackPattern
{
[Export] public BulletResource BulletResource { get; set; }
[Export] public Vector3 EmitterOffset { get; set; } = Vector3.Zero;
[Export] public int bulletCount = 16;
[Export] public float rotationSpeed = 0f;
[Export] public float _rotationOffset = 0f;
[Export] public float duration = 5f;
[Export] public float spread = 360f;
[Export] public float burstInterval = 0.5f;
[ExportCategory("Burst")] [Export] public int ShotsPerBurst { get; private set; } = 100;
[Export] public float BurstRate { get; private set; } = 0f;
[ExportCategory("Other")] [Export] public bool _targetPlayer = false;
[ExportCategory("Other")] [Export] public bool _predictPlayer = false;
[ExportCategory("Overrides")]
[Export] public bool OverrideOwner { get; private set; } = false;
[Export] public BulletOwner Owner { get; private set; } = BulletOwner.None;
[Export] public bool OverrideDamageType { get; private set; } = false;
[Export] public DamageType DamageType { get; private set; } = DamageType.Neutral;
[Export] public bool OverrideControllable { get; private set; } = false;
[Export] public bool Controllable { get; private set; } = false;
[ExportCategory("Extra Modifiers")]
[Export]
public bool OverrideCreationModifier { get; private set; } = false;
[Export] public BulletCreationModifier Modifier;
[Export] public Array<TimeModifier> TimeModifiers = [];
protected virtual BulletInfo MakeBullet(Vector2 position, int count = 1, float spread = 0f, float rotationOffset = 0f)
{
var bullet = this.BulletResource.MakeBullet(position, count,
spread, rotationOffset);
if (OverrideOwner)
{
bullet.Owner = this.Owner;
}
if (OverrideDamageType)
{
bullet.DamageType = DamageType;
}
if (OverrideControllable)
{
bullet.Controllabe = Controllable;
}
if (OverrideCreationModifier)
{
bullet.Modifier = this.Modifier;
}
if (TimeModifiers.Count != 0)
{
bullet.TimeModifiers.AddRange(TimeModifiers);
}
return bullet;
}
public override IPatternMachine MakeMachine(Node parent)
{
return new SpiralPatternMachine(this, parent);
}
public class SpiralPatternMachine(ShootingPattern3D pattern, Node parent) : IPatternMachine
{
public Node Parent => parent;
public IScriptHost3D ScriptHost { get; private set; }
private double timer;
private double burstTimer;
//private double _burstRateTimer;
private BulletSpawner3D spawner;
private ShootStatus _state = ShootStatus.Idle;
private int _burstBullets;
public void Start()
{
ScriptHost = Parent as IScriptHost3D;
timer = 0;
_burstBullets = pattern.ShotsPerBurst;
burstTimer = pattern.burstInterval; // start immediately
spawner = parent.GetNode<BulletSpawner3D>("BulletSpawner3D");
_state = ShootStatus.Shooting;
}
private void WaitingBurstUpdate(double delta)
{
timer += delta;
burstTimer += delta;
if (burstTimer >= pattern.burstInterval)
{
_state = ShootStatus.Shooting;
}
}
private void WaitingReloadUpdate(double delta)
{
timer += delta;
burstTimer += delta;
if (burstTimer >= pattern.BurstRate)
{
_burstBullets = pattern.ShotsPerBurst;
_state = ShootStatus.Shooting;
}
}
private void ShootingUpdate(double delta)
{
timer += delta;
burstTimer = 0;
Shoot();
_burstBullets--;
if (_burstBullets <= 0)
{
_state = ShootStatus.WaitingReload;
}
else
{
_state = ShootStatus.WaitingBurst;
}
}
public void UpdatePattern(double delta)
{
switch (_state)
{
case ShootStatus.Idle:
case ShootStatus.Done:
return;
case ShootStatus.Shooting:
ShootingUpdate(delta);
break;
case ShootStatus.WaitingBurst:
WaitingBurstUpdate(delta);
break;
case ShootStatus.WaitingReload:
WaitingReloadUpdate(delta);
break;
}
if (timer >= pattern.duration)
{
_state = ShootStatus.Done;
}
}
private void Shoot()
{
float angleOffset = pattern._rotationOffset + (float)(pattern.rotationSpeed * timer);
Vector2 direction = pattern.BulletResource.Direction;
// TODO: Fix player aiming for 3D
// if (pattern._targetPlayer && GameManager.Instance.PlayerPosition.HasValue)
// {
// if (pattern._predictPlayer && GameManager.Instance.PlayerVelocity.HasValue)
// {
// var predictedDirection = MathFunctions.PredictInterceptPosition(ScriptHost.ParentObject.GlobalPosition,
// GameManager.Instance.PlayerPosition.Value, GameManager.Instance.PlayerVelocity.Value,
// pattern.BulletResource.BulletSpeed);
// if (predictedDirection.HasValue)
// {
// direction = (predictedDirection.Value - ScriptHost.ParentObject.GlobalPosition).Normalized();
// }
// }
// else
// {
// direction = (GameManager.Instance.PlayerPosition.Value - ScriptHost.ParentObject.GlobalPosition).Normalized();
// }
// }
var spawnPosition = ScriptHost.ParentObject.GlobalPosition + pattern.EmitterOffset;
var bullet = pattern.MakeBullet(spawnPosition.ToVector2(), pattern.bulletCount,
pattern.spread, angleOffset);
bullet.Direction = direction;
//spawner.SpawnBullet(MakeBullet(Boss.GlobalPosition, direction, angleOffset));
spawner.SpawnBullet(bullet, spawnPosition);
}
public bool IsComplete()
{
//return timer >= pattern.duration;
return _state is ShootStatus.Done;
}
private enum ShootStatus
{
Idle,
Shooting,
WaitingBurst,
WaitingReload,
Done
}
}
}

View file

@ -0,0 +1 @@
uid://bxiprx5nwmpnu

View file

@ -0,0 +1,52 @@
using Cirno.Scripts.AttackPatterns;
using Godot;
using Godot.Collections;
namespace Cirno.Scripts.Resources.BulletScripts;
[GlobalClass]
[Tool]
public partial class BulletScript3D : Resource
{
[Export]
public Array<AttackPattern> Patterns { get; private set; }
public BulletScriptMachine Make(Node parent)
{
return new BulletScriptMachine(parent, Patterns);
}
public class BulletScriptMachine(Node parent, Array<AttackPattern> patterns)
{
private int _currentPatternIndex = 0;
//private double _patternTimer;
private AttackPattern CurrentPattern => patterns[_currentPatternIndex];
private IPatternMachine _currentPatternMachine;
public void Start()
{
if (patterns.Count == 0) return;
_currentPatternIndex = 0;
_currentPatternMachine = CurrentPattern.MakeMachine(parent);
_currentPatternMachine.Start();
}
public void UpdatePhase(double delta)
{
//_patternTimer += delta;
_currentPatternMachine.UpdatePattern(delta);
//CurrentPattern.UpdatePattern(delta);
if (!CurrentPattern.WaitForCompletion || _currentPatternMachine.IsComplete())
{
_currentPatternIndex = (_currentPatternIndex + 1) % patterns.Count;
_currentPatternMachine = CurrentPattern.MakeMachine(parent);
_currentPatternMachine.Start();
}
}
}
}

View file

@ -0,0 +1 @@
uid://w8hcpu68ssq

View file

@ -8,4 +8,9 @@ public static class VectorExtensions
{
return new Vector2(original.X, original.Z);
}
public static Vector3 ToVector3(this Vector2 original, float height = 0)
{
return new Vector3(original.X, height, original.Y);
}
}