从零开发游戏需要学习的c#模块,第二十三章(粒子效果 —— 让游戏“活”起来本课目标)

本节课学习目标

  1. 创建粒子系统类

  2. 吃金币时生成金色星星粒子

  3. 碰敌人时生成红色火花粒子

  4. 粒子自动衰减、消失

第一步:创建粒子类

右键项目 → 添加 ,文件名 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  // 颜色 × 透明度

本节课学习内容到此结束,我叫魔法阵维护师,关注我,下期更精彩!

相关推荐
Shan120510 小时前
三分查找经典实例分析与学习
学习
心中有国也有家10 小时前
CANN 学习新范式:cann-learning-hub 如何让昇腾入门不再「劝退」
人工智能·经验分享·笔记·学习·算法
GISer_Jing10 小时前
前端全流程求职Skill 攻略
前端·学习·前端框架
_Evan_Yao10 小时前
数据结构太难了?用画图的方式理解链表和栈和树和图
数据结构·学习·链表
一只大袋鼠10 小时前
SpringBoot 入门学习笔记(三)Web 开发下篇
spring boot·笔记·学习
承渊政道10 小时前
Linux系统学习【进程概念从入门到深入理解】
linux·服务器·笔记·学习·ubuntu·系统架构·bash
魔法阵维护师10 小时前
从零开发游戏需要学习的c#模块,第二十二章(音效与背景音乐)
学习·游戏·c#
心中有国也有家11 小时前
hixl:昇腾分布式推理的「快递专线」
人工智能·经验分享·笔记·分布式·学习·算法
爱睡懒觉的焦糖玛奇朵18 小时前
【从视频到数据集:焦糖玛奇朵的魔法工具使用说明】
人工智能·python·深度学习·学习·算法·yolo·音视频