从零开发游戏需要学习的c#模块,第二十三章(粒子效果 —— 让游戏“活”起来本课目标)
本节课学习目标创建粒子系统类吃金币时生成金色星星粒子碰敌人时生成红色火花粒子粒子自动衰减、消失第一步创建粒子类右键项目 →添加→类文件名Particle.csusing 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.csusing Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; namespace MY_FIRST_GAME { public class ParticleSystem { private ListParticle particles new ListParticle(); 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 ListVector2 coins; private Random rng; private int score; private Texture2D enemyTexture; private ListVector2 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 ListVector2(); enemies new ListVector2(); 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. 粒子物理csharpPosition Velocity * deltaTime; // 按速度移动 Velocity * 0.98f; // 速度逐渐衰减 Lifetime - deltaTime; // 生命倒计时2. 圆形纹理生成csharpfloat dist sqrt(dx² dy²); if (dist radius) alpha 1 - (dist / radius); // 边缘透明3. 随机发射方向csharpfloat angle random * PI * 2; Vector2 velocity (cos(angle) * speed, sin(angle) * speed);4. 颜色混合csharpColor.Lerp(Color.Red, Color.Orange, random); // 红橙之间随机 p.Color * p.Alpha // 颜色 × 透明度本节课学习内容到此结束我叫魔法阵维护师关注我下期更精彩