Full source code:
http://www.mediafire.com/?wgimixyjtbt---
I'm making a bullet hell game (danmaku) using SFML.NET and C#.
My source of inspiration is Danmakufu, a game where you can create your bullet patterns, share them, play them, etc.
In Danmakufu I can easily spawn 300 bullets every frame staying at 58-60FPS all the time.
In my own game, when I spawn 300 bullets every 3 frames, the game becomes unplayable due to slowdown.
The biggest difference between my game and Danmakufu is that my game's resolution is 1024x768, Danmakufu is 800x600. But can that make an enormous difference?
---
My code for bullets is this:
public class Entity
{
public float X { get; set; }
public float Y { get; set; }
public Game Game { get; set; }
public bool Alive { get; set; }
public Sprite Sprite { get; set; }
public Entity(Game game)
{
Game = game;
Alive = true;
}
public virtual void Update()
{
Sprite.Position = new Vector2(X, Y);
}
public virtual void Draw()
{
Game.RenderWindow.Draw(Sprite);
}
public virtual void Destroy()
{
Alive = false;
}
}
Entity is the basic class for any object in the game; you can draw and update entities.
public class Bullet : Entity
{
private float _angle;
public float Angle
{
set { _angle = Data.ANormalize(Data.ARadians(value)); }
get { return Data.ADegrees(_angle); }
}
public float Speed { get; set; }
public Bullet(Game game) : base(game)
{
Game.BulletManager.Entities.Add(this);
}
public override void Draw()
{
base.Draw();
}
public override void Update()
{
base.Update();
X = X + Speed * (float)Math.Cos(_angle);
Y = Y + Speed * (float)Math.Sin(_angle);
Sprite.Rotation = 360-Angle;
if(Game.Playfield.IsInPlayfield(X, Y) == false)
{
Destroy();
}
}
public override void Destroy()
{
base.Destroy();
Game.BulletManager.Entities.Remove(this);
}
}
The Bullet class inherits from Entity. When it is spawned, it is added in the Game.BulletManager.Entities list. When it is deleted, it is removed from the same list.
If you're curious about the stuff happening in the "Angle" property, here are the related methods:
public static float ARadians(float angle)
{
return angle * 3.14f / 180f;
}
public static float ADegrees(float angle)
{
return angle * 180f / 3.14f;
}
public static float ANormalize(float angle)
{
if (angle > 360)
{
angle = angle - (360 * Convert.ToInt32((angle / 360)));
}
if (angle < 0)
{
angle = angle + (360 * Convert.ToInt32((angle / 360)));
}
return angle;
}
---
Here's the code for the Manager class and the Game class:
public class Manager
{
public List<Entity> Entities { get; set; }
public Manager()
{
Entities = new List<Entity>();
}
public void Update()
{
for (int index = 0; index < Entities.Count; index++)
{
Entity entity = Entities[index];
if (entity.Alive)
{
entity.Update();
}
}
}
public void Draw()
{
for (int index = 0; index < Entities.Count; index++)
{
Entity entity = Entities[index];
if (entity.Alive)
{
entity.Draw();
}
}
}
}
Pretty straightforward. Updating a manager updates all of its entities; drawing a manager draws all of its entities.
public class Game
{
public bool Running { get; set; }
public RenderWindow RenderWindow { get; set; }
public Manager BulletManager { get; set; }
public Playfield Playfield { get; set; }
public int Frame { get; set; }
//DEBUG---
public int DebugFrame { get; set; }
private int kk = 0;
private int ff = 1;
private bool kkb = true;
Image image = new Image(Environment.CurrentDirectory + @"/Data/Images/b_pellet.png");
//--------
public Game()
{
Running = true;
RenderWindow = new RenderWindow(new VideoMode(1024,768), "Bullet Hell Test");
RenderWindow.UseVerticalSync(true);
RenderWindow.Show(true);
BulletManager = new Manager();
Playfield = new Playfield(this, 250, 50, 774, 718) {Offset = 25};
Frame = 0;
Debug();
Run();
}
public void Run()
{
while(Running)
{
Update();
Draw();
}
}
public void Update()
{
BulletManager.Update();
if(DebugFrame == 10)
{
Debug();
DebugFrame = 0;
}
DebugFrame++;
Frame++;
Console.WriteLine("{0} --- {1}", Convert.ToInt32(RenderWindow.GetFrameTime() * 1000), BulletManager.Entities.Count);
}
public void Draw()
{
RenderWindow.Clear(Color.White);
Playfield.DrawInside();
BulletManager.Draw();
Playfield.DrawOutside();
RenderWindow.Display();
}
//--------------------------------------------------------------------------------------------
public Bullet CreateBullet()
{
return new Bullet(this);
}
//--------------------------------------------------------------------------------------------
public void Debug()
{
float step = 300;
for(int i = 0; i < step; i++)
{
Bullet temp = new Bullet(this);
temp.Angle = (i*(360/step));
temp.Speed = 5f;
temp.Sprite = new Sprite(image);
temp.X = Playfield.GetCenter().X;
temp.Y = Playfield.GetCenter().Y;
}
if (kk < 120)
{
kk = kk + 5;
}
else
{
kk = 120;
}
ff = ff + 22;
}
}
This one is pretty straightforward too, ignoring the debug variables that I used before to create cool-looking patterns.
Update calls the "Debug" method every 10 frames, which spawns bullets.
Bullets aren't spawned by recycling, but instead by creating "new" bullets. I've chosen this because I haven't noticed big performance improvements recycling bullets in the past.
Draw draws bullets and the playfield, which creates the usual "shoot 'em up" borders around the playing screen.
---
So, I've created this thread to kindly ask you what am I doing wrong; why can't my game spawn the same amount of bullets as Danmakufu without slowing down?
Is it a SFML.NET limitation?
---
I have also another problem:
Game.BulletManager.Entities.Remove(this); --- this line in the Bullet -> Destroy method makes a single bullet move in a strange way while uncommented.
I think this is because the for loops skips or makes an unnecessary additional cycle.
How can I prevent this?
---
Thanks.