从零开发游戏需要学习的c#模块,第二十三章(粒子效果 —— 让游戏“活”起来本课目标)
本节课学习目标
创建粒子系统类
吃金币时生成金色星星粒子
碰敌人时生成红色火花粒子
粒子自动衰减、消失
第一步:创建粒子类
右键项目 →添加→类,文件名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:
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完整替换为:
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 // 颜色 × 透明度
本节课学习内容到此结束,我叫魔法阵维护师,关注我,下期更精彩!
