I've been trying to better my lesser side of knowledge in the C# programming language by tackling graphics head on. I've learned a little bit, but I'm getting stumped right now because I can't explain 100% what's going on here. I've developed a Pong game to teach myself how to use the Graphics class; in this game I want particles to fly off into random locations when the ball hits a paddle or wall. This much I've managed myself, however I've encountered a problem. My particles are going away too quickly and are a position behind the ball. Below is my source code with the multiple classes I'm using. (Please note I've ported the Vector2 class from XNA and called it BallVelocity.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;
using System.Drawing;
namespace PONG
{
public partial class Form1 : Form
{
// Threads
private Thread ParticleThread;
private Thread GameThread;
// Game State
private GameState gsState = new GameState();
// Objects
private Rectangle rBall;
private Rectangle rLeftPlayer;
private Rectangle rRightPlayer;
// Aspect Ratio
private const int iWidth = 820;
private const int iHeight = 422;
// Movement
private int iLeftY = iHeight / 2 - 50;
private int iRightY = iHeight / 2 - 50;
private int iBallX = iWidth / 2 - 5;
private int iBallY = iHeight / 2 - 5;
// Velocity
private BallVelocity bvVelocity = BallVelocity.Zero;
// Scoring
private int[] iaScores = new int[2];
// Game Started and Activate Particles Booleans
private bool bGameStarted = false;
private bool ActivateParticles = false;
public Form1()
{
InitializeComponent();
CreateEvents();
SetGraphics();
}
internal void ParticleEngine()
{
try
{
for (; ; )
{
Thread.Sleep(50);
List<Particle> ToBeRemoved = new List<Particle>();
lock (gsState.Particles)
{
foreach (Particle p in gsState.Particles)
{
if (p.PerformFrame(gsState))
ToBeRemoved.Add(p);
}
}
foreach (var p in ToBeRemoved)
{
gsState.Particles.Remove(p);
}
}
}
catch (ThreadAbortException ex)
{
Debug.Print("Game ending.");
}
}
private void CreateEvents()
{
this.KeyDown += new KeyEventHandler(MovePlayer);
}
private void SetGraphics()
{
rBall = new Rectangle(iWidth / 2, iHeight / 2, 15, 15);
rLeftPlayer = new Rectangle(5, iHeight / 2, 15, 100);
rRightPlayer = new Rectangle(iWidth - 15 - 5, iHeight / 2, 15, 100);
}
private void MovePlayer(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Up)
iLeftY -= 5;
else if (e.KeyCode == Keys.Down)
iLeftY += 5;
rLeftPlayer = new Rectangle(5, iLeftY, 15, 100);
}
private void UpdateBall()
{
rBall.X += (int)bvVelocity.X;
rBall.Y += (int)bvVelocity.Y;
if (rBall.Y < 0 || rBall.Y + rBall.Height > iHeight)
{
bvVelocity.Y = -bvVelocity.Y;
PointF BallLocation = new PointF(rBall.X, rBall.Y);
for (int i = 0; i < 25; i++)
{
Particle NewParticle = new Particle(BallLocation, 1);
gsState.Particles.Add(NewParticle);
}
ActivateParticles = true;
}
if (rBall.IntersectsWith(rLeftPlayer) || rBall.IntersectsWith(rRightPlayer))
{
bvVelocity.X = -bvVelocity.X;
PointF BallLocation = new PointF(rBall.X, rBall.Y);
for (int i = 0; i < 25; i++)
{
Particle NewParticle = new Particle(BallLocation, 1);
gsState.Particles.Add(NewParticle);
}
ActivateParticles = true;
}
if (rBall.X < 0)
{
iaScores[1]++;
InitiateBall();
}
else if (rBall.X + rBall.Width > iWidth)
{
iaScores[0]++;
InitiateBall();
}
}
private void InitiateBall()
{
int iSpeed = 15;
Random rRandom = new Random();
switch (rRandom.Next(4))
{
case 0:
bvVelocity.X = iSpeed;
bvVelocity.Y = iSpeed;
break;
case 1:
bvVelocity.X = -iSpeed;
bvVelocity.Y = iSpeed;
break;
case 2:
bvVelocity.X = iSpeed;
bvVelocity.Y = -iSpeed;
break;
case 3:
bvVelocity.X = -iSpeed;
bvVelocity.Y = -iSpeed;
break;
}
rBall.X = iWidth / 2 - 5;
rBall.Y = iHeight / 2 - 5;
}
private void mComputer()
{
if (rBall.X > rRightPlayer.X - 200)
{
if (rRightPlayer.Y > rBall.Y)
rRightPlayer.Y = rRightPlayer.Y - 5;
else
rRightPlayer.Y = rRightPlayer.Y + 5;
}
else
{
if (rRightPlayer.Y == iHeight / 2)
rRightPlayer.Y = iHeight / 2;
else if (rRightPlayer.Y > iHeight / 2)
rRightPlayer.Y = rRightPlayer.Y - 5;
else
rRightPlayer.Y = rRightPlayer.Y + 5;
}
}
private void PaintObjectsAndKeepScore(object sender, PaintEventArgs e)
{
e.Graphics.DrawRectangle(new Pen(Brushes.Lime), rLeftPlayer);
e.Graphics.FillRectangle(Brushes.Green, rLeftPlayer);
e.Graphics.DrawRectangle(new Pen(Brushes.Red), rRightPlayer);
e.Graphics.FillRectangle(Brushes.Maroon, rRightPlayer);
e.Graphics.DrawEllipse(new Pen(Brushes.Gray), rBall);
e.Graphics.FillEllipse(Brushes.White, rBall);
if (ActivateParticles)
{
lock (gsState.Particles)
{
foreach (Particle p in gsState.Particles)
p.DrawParticles(e.Graphics);
}
ActivateParticles = false;
}
}
private void Form1_Load(object sender, EventArgs e)
{
ParticleThread = new Thread(ParticleEngine);
ParticleThread.Start();
InitiateBall();
Updater.Start();
}
private void EndGame(object sender, FormClosingEventArgs e)
{
ParticleThread.Abort();
ParticleThread = null;
}
private void UpdateGame(object sender, EventArgs e)
{
UpdateBall();
mComputer();
Invoke((MethodInvoker)(() =>
{
this.Invalidate();
this.Update();
}));
}
}
internal class Particle
{
private TimeSpan ParticleLife = new TimeSpan(0, 0, 3);
private DateTime? ParticleBirth;
internal PointF Location { get; private set; }
internal PointF Velocity { get; private set; }
internal static Random Random = new Random();
internal static PointF RandomVector(float Speed)
{
float RandomAngle = (float)(Random.NextDouble() * (Math.PI * 2));
Speed *= (float)(Random.NextDouble());
return new PointF((float)Math.Sin(RandomAngle) * Speed, (float)Math.Cos(RandomAngle) * Speed);
}
internal Particle(PointF Location, float Speed)
: this(Location, RandomVector(Speed))
{
}
internal Particle(PointF Location, PointF Velocity)
{
this.Location = Location;
this.Velocity = Velocity;
}
internal bool PerformFrame(GameState gs)
{
if (ParticleBirth == null) ParticleBirth = DateTime.Now;
Location = new PointF(Location.X + Velocity.X, Location.Y + Velocity.Y);
Velocity = new PointF(Velocity.X * 0.99f, Velocity.Y * 0.99f);
return DateTime.Now - ParticleBirth > ParticleLife;
}
internal void DrawParticles(Graphics g)
{
g.DrawRectangle(new Pen(Brushes.White), Location.X - 2, Location.Y - 2, 5, 5);
}
}
internal class GameState
{
internal List<Particle> Particles;
internal GameState()
{
Particles = new List<Particle>();
}
}
internal class BallVelocity
{
internal int X;
internal int Y;
internal static BallVelocity Zero = new BallVelocity(0, 0);
internal BallVelocity(int X, int Y)
{
this.X = X;
this.Y = Y;
}
}
}
All help is greatly appreciated on this matter.
Thanks,
Jamie