本节课学习目标
-
创建粒子系统类
-
吃金币时生成金色星星粒子
-
碰敌人时生成红色火花粒子
-
粒子自动衰减、消失
第一步:创建粒子类
右键项目 → 添加 → 类 ,文件名 Particle.cs:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
namespace MY_FIRST_GAME
{
public class Particle
{
public Vector2 Position;
public Vector2 Velocity; // 速度(方向和快慢)
public float Lifetime; // 剩余生命(秒)
public float MaxLifetime; // 最大生命(秒)
public Color Color;
public float Size; // 粒子大小
public float Rotation; // 旋转角度
public float RotationSpeed; // 旋转速度
public bool IsAlive => Lifetime > 0;
public Particle(Vector2 position, Vector2 velocity, float lifetime, Color color, float size)
{
Position = position;
Velocity = velocity;
Lifetime = lifetime;
MaxLifetime = lifetime;
Color = color;
Size = size;
Rotation = 0f;
RotationSpeed = (float)(new Random().NextDouble() * 10 - 5); // 随机旋转
}
public void Update(float deltaTime)
{
Lifetime -= deltaTime;
Position += Velocity * deltaTime;
Velocity *= 0.98f; // 速度衰减
Rotation += RotationSpeed * deltaTime;
}
// 透明度随生命值变化
public float Alpha => Math.Clamp(Lifetime / MaxLifetime, 0f, 1f);
}
}
第二步:创建粒子系统类
右键项目 → 添加 → 类 ,文件名 ParticleSystem.cs:
cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
namespace MY_FIRST_GAME
{
public class ParticleSystem
{
private List<Particle> particles = new List<Particle>();
private Texture2D particleTexture;
private Random rng = new Random();
public ParticleSystem(GraphicsDevice graphicsDevice)
{
// 创建圆形粒子纹理
int size = 16;
particleTexture = new Texture2D(graphicsDevice, size, size);
Color[] data = new Color[size * size];
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
float dx = x - size / 2f;
float dy = y - size / 2f;
float dist = (float)Math.Sqrt(dx * dx + dy * dy);
if (dist < size / 2f)
{
float alpha = 1f - (dist / (size / 2f));
data[y * size + x] = new Color(1f, 1f, 1f, alpha);
}
else
{
data[y * size + x] = Color.Transparent;
}
}
}
particleTexture.SetData(data);
}
// ★ 生成金币粒子(金色星星效果)
public void EmitCoinParticles(Vector2 position, int count = 8)
{
for (int i = 0; i < count; i++)
{
float angle = (float)(rng.NextDouble() * Math.PI * 2);
float speed = (float)(rng.NextDouble() * 150 + 50);
Vector2 velocity = new Vector2(
(float)Math.Cos(angle) * speed,
(float)Math.Sin(angle) * speed - 80 // 稍微向上
);
float lifetime = (float)(rng.NextDouble() * 0.5 + 0.3);
Color color = rng.Next(2) == 0 ? Color.Gold : Color.Yellow;
particles.Add(new Particle(position, velocity, lifetime, color, 12));
}
}
// ★ 生成敌人碰撞粒子(红色火花效果)
public void EmitHitParticles(Vector2 position, int count = 12)
{
for (int i = 0; i < count; i++)
{
float angle = (float)(rng.NextDouble() * Math.PI * 2);
float speed = (float)(rng.NextDouble() * 200 + 80);
Vector2 velocity = new Vector2(
(float)Math.Cos(angle) * speed,
(float)Math.Sin(angle) * speed
);
float lifetime = (float)(rng.NextDouble() * 0.4 + 0.2);
Color color = Color.Lerp(Color.Red, Color.Orange, (float)rng.NextDouble());
particles.Add(new Particle(position, velocity, lifetime, color, 10));
}
}
public void Update(float deltaTime)
{
for (int i = particles.Count - 1; i >= 0; i--)
{
particles[i].Update(deltaTime);
if (!particles[i].IsAlive)
particles.RemoveAt(i);
}
}
public void Draw(SpriteBatch spriteBatch)
{
foreach (Particle p in particles)
{
spriteBatch.Draw(
particleTexture,
p.Position,
null,
p.Color * p.Alpha,
p.Rotation,
new Vector2(particleTexture.Width / 2, particleTexture.Height / 2),
p.Size / particleTexture.Width,
SpriteEffects.None,
0f
);
}
}
public int ParticleCount => particles.Count;
}
}
第三步:集成到 Game1.cs
把 Game1.cs 完整替换为:
cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Audio;
using System;
using System.Collections.Generic;
using System.IO;
using FontStashSharp;
namespace MY_FIRST_GAME
{
public enum GameState { Exploring, Battling }
public class Game1 : Game
{
private GraphicsDeviceManager _graphics;
private SpriteBatch _spriteBatch;
private Player player = default!;
private Texture2D playerSpriteSheet;
private Texture2D coinTexture;
private List<Vector2> coins;
private Random rng;
private int score;
private Texture2D enemyTexture;
private List<Vector2> enemies;
private SpriteFontBase font;
private GameState state = GameState.Exploring;
// 音频
private SoundEffect coinSound = default!;
private SoundEffect hitSound = default!;
// ★ 粒子系统
private ParticleSystem particleSystem = default!;
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = true;
}
protected override void Initialize()
{
_graphics.PreferredBackBufferWidth = 800;
_graphics.PreferredBackBufferHeight = 600;
_graphics.ApplyChanges();
rng = new Random();
coins = new List<Vector2>();
enemies = new List<Vector2>();
score = 0;
SpawnCoins(5);
SpawnEnemies(3);
// ★ 初始化粒子系统
particleSystem = new ParticleSystem(GraphicsDevice);
base.Initialize();
}
protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);
// 玩家
using var stream = File.OpenRead("Content/player_spritesheet.png");
playerSpriteSheet = Texture2D.FromStream(GraphicsDevice, stream);
player = new Player(playerSpriteSheet, new Vector2(400, 300));
// 金币
coinTexture = new Texture2D(GraphicsDevice, 24, 24);
Color[] coinData = new Color[24 * 24];
for (int i = 0; i < coinData.Length; i++) coinData[i] = Color.Gold;
coinTexture.SetData(coinData);
// 敌人
enemyTexture = new Texture2D(GraphicsDevice, 40, 40);
Color[] enemyData = new Color[40 * 40];
for (int i = 0; i < enemyData.Length; i++) enemyData[i] = Color.Red;
enemyTexture.SetData(enemyData);
// 字体
var fontSystem = new FontSystem();
fontSystem.AddFont(File.ReadAllBytes("Content/consola.ttf"));
font = fontSystem.GetFont(18);
// 音效
try
{
using var coinStream = File.OpenRead("Content/coin.wav");
coinSound = SoundEffect.FromStream(coinStream);
using var hitStream = File.OpenRead("Content/hit.wav");
hitSound = SoundEffect.FromStream(hitStream);
}
catch { }
}
private void SpawnCoins(int count)
{
for (int i = 0; i < count; i++)
coins.Add(new Vector2(rng.Next(50, 750), rng.Next(50, 550)));
}
private void SpawnEnemies(int count)
{
for (int i = 0; i < count; i++)
enemies.Add(new Vector2(rng.Next(80, 720), rng.Next(80, 520)));
}
protected override void Update(GameTime gameTime)
{
float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
KeyboardState keyboard = Keyboard.GetState();
if (state == GameState.Exploring)
{
player.Update(deltaTime);
CheckCoinCollision();
CheckEnemyCollision();
if (coins.Count == 0) SpawnCoins(5);
if (enemies.Count == 0) SpawnEnemies(3);
}
// ★ 更新粒子
particleSystem.Update(deltaTime);
if (keyboard.IsKeyDown(Keys.M))
SoundEffect.MasterVolume = SoundEffect.MasterVolume > 0 ? 0f : 1f;
if (keyboard.IsKeyDown(Keys.Escape)) Exit();
base.Update(gameTime);
}
private void CheckCoinCollision()
{
Rectangle playerRect = player.GetBounds();
for (int i = coins.Count - 1; i >= 0; i--)
{
Rectangle coinRect = new Rectangle((int)coins[i].X, (int)coins[i].Y, 24, 24);
if (playerRect.Intersects(coinRect))
{
Vector2 coinPos = coins[i];
coins.RemoveAt(i);
score += 10;
// ★ 生成金币粒子
particleSystem.EmitCoinParticles(coinPos);
try { coinSound?.Play(); } catch { }
}
}
}
private void CheckEnemyCollision()
{
Rectangle playerRect = player.GetBounds();
for (int i = enemies.Count - 1; i >= 0; i--)
{
Rectangle enemyRect = new Rectangle(
(int)enemies[i].X - 20, (int)enemies[i].Y - 20, 40, 40);
if (playerRect.Intersects(enemyRect))
{
Vector2 enemyPos = enemies[i];
enemies.RemoveAt(i);
score += 50;
// ★ 生成碰撞粒子
particleSystem.EmitHitParticles(enemyPos);
try { hitSound?.Play(); } catch { }
}
}
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
_spriteBatch.Begin();
// 金币
foreach (Vector2 coinPos in coins)
_spriteBatch.Draw(coinTexture, coinPos, Color.White);
// 敌人
foreach (Vector2 enemyPos in enemies)
_spriteBatch.Draw(enemyTexture, enemyPos, null, Color.White,
0f, new Vector2(20, 20), 1f, SpriteEffects.None, 0f);
// ★ 粒子(画在玩家下面)
particleSystem.Draw(_spriteBatch);
// 玩家
player.Draw(_spriteBatch);
// UI
_spriteBatch.DrawString(font, $"分数:{score}", new Vector2(10, 10), Color.White);
_spriteBatch.DrawString(font, $"金币:{coins.Count}", new Vector2(10, 35), Color.Gold);
_spriteBatch.DrawString(font, $"敌人:{enemies.Count}", new Vector2(10, 60), Color.Red);
_spriteBatch.DrawString(font, $"粒子:{particleSystem.ParticleCount}", new Vector2(10, 85), Color.Cyan);
_spriteBatch.DrawString(font, "WASD移动 | M静音 | ESC退出", new Vector2(10, 570), Color.LightGray);
_spriteBatch.End();
base.Draw(gameTime);
}
}
}
本节课新知识
1. 粒子物理
csharp
Position += Velocity * deltaTime; // 按速度移动
Velocity *= 0.98f; // 速度逐渐衰减
Lifetime -= deltaTime; // 生命倒计时
2. 圆形纹理生成
csharp
float dist = sqrt(dx² + dy²);
if (dist < radius)
alpha = 1 - (dist / radius); // 边缘透明
3. 随机发射方向
csharp
float angle = random * PI * 2;
Vector2 velocity = (cos(angle) * speed, sin(angle) * speed);
4. 颜色混合
csharp
Color.Lerp(Color.Red, Color.Orange, random); // 红橙之间随机
p.Color * p.Alpha // 颜色 × 透明度
本节课学习内容到此结束,我叫魔法阵维护师,关注我,下期更精彩!