从零开发游戏需要学习的c#模块,第二十七章(远程攻击 —— 发射子弹)

本节课目标

  1. 按空格键向鼠标方向发射子弹

  2. 子弹碰到敌人后,敌人扣血

  3. 子弹碰到墙壁后消失

  4. 敌人被子弹打死才消失(不再碰撞扣血)

第一步:创建子弹类

右键项目 → 添加 ,文件名 Bullet.cs

csharp

复制代码
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace MY_FIRST_GAME
{
    public class Bullet
    {
        public Vector2 Position;
        public Vector2 Velocity;
        public float Speed = 400f;
        public int Damage = 20;
        public bool IsAlive = true;
        public float Lifetime = 2f; // 最多存在2秒

        private Texture2D texture;
        private float timer;

        public Bullet(Vector2 startPosition, Vector2 direction, GraphicsDevice graphicsDevice)
        {
            Position = startPosition;
            Velocity = direction * Speed;

            // 创建8x8的黄色子弹纹理
            texture = new Texture2D(graphicsDevice, 8, 8);
            Color[] data = new Color[8 * 8];
            for (int y = 0; y < 8; y++)
                for (int x = 0; x < 8; x++)
                {
                    float dx = x - 3.5f;
                    float dy = y - 3.5f;
                    if (dx * dx + dy * dy < 16)
                        data[y * 8 + x] = Color.Yellow;
                    else
                        data[y * 8 + x] = Color.Transparent;
                }
            texture.SetData(data);
        }

        public void Update(float deltaTime, TileMap tileMap)
        {
            Position += Velocity * deltaTime;
            timer += deltaTime;

            // 超时或撞墙就消失
            if (timer >= Lifetime || tileMap.IsWall(Position))
                IsAlive = false;
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(texture, Position, null, Color.White,
                0f, new Vector2(4, 4), 1f, SpriteEffects.None, 0f);
        }

        public Rectangle GetBounds()
        {
            return new Rectangle((int)Position.X - 4, (int)Position.Y - 4, 8, 8);
        }
    }
}

第二步:给 Player 类添加攻击方法

Player.cs 里添加:

cs 复制代码
// 添加字段
private float attackCooldown;
private float attackTimer;

// 在构造函数里初始化
attackCooldown = 0.3f;  // 0.3秒攻击间隔
attackTimer = 0f;

// 添加方法:返回是否应该发射子弹
public bool TryAttack(float deltaTime)
{
    attackTimer += deltaTime;
    if (attackTimer >= attackCooldown)
    {
        attackTimer = 0f;
        return true;
    }
    return false;
}

// 获取瞄准方向(指向鼠标)
public Vector2 GetAimDirection(Vector2 mouseWorldPosition)
{
    Vector2 direction = mouseWorldPosition - Position;
    if (direction.Length() > 0)
        direction.Normalize();
    else
        direction = new Vector2(1, 0); // 默认向右
    return direction;
}

第三步:在 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 class Game1 : Game
    {
        private GraphicsDeviceManager _graphics;
        private SpriteBatch _spriteBatch;

        private SceneManager sceneManager = default!;
        private Camera camera = default!;

        private Player player = default!;
        private Texture2D playerSpriteSheet;

        private TileMap tileMap = default!;
        private Texture2D coinTexture;
        private List<Vector2> coins = default!;
        private Random rng = default!;
        private int score;

        private List<Enemy> enemies = default!;

        private SpriteFontBase font = default!;
        private SpriteFontBase titleFont = default!;

        private SoundEffect coinSound = default!;
        private SoundEffect hitSound = default!;
        private SoundEffect shootSound = default!;

        private ParticleSystem particleSystem = default!;
        private List<Bullet> bullets = default!;

        private float titleTimer;
        private SaveData saveData = default!;
        private int coinsCollectedThisGame;
        private int enemiesDefeatedThisGame;
        private bool isNewHighScore;

        private MouseState currentMouse;
        private MouseState previousMouse;

        public Game1()
        {
            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            IsMouseVisible = true;
        }

        protected override void Initialize()
        {
            _graphics.PreferredBackBufferWidth = 800;
            _graphics.PreferredBackBufferHeight = 600;
            _graphics.ApplyChanges();

            sceneManager = new SceneManager();
            rng = new Random();
            saveData = SaveManager.Load();
            camera = new Camera(800, 600, 1600, 1200);

            InitializeGame();

            base.Initialize();
        }

        private void InitializeGame()
        {
            tileMap = new TileMap(GraphicsDevice);
            coins = new List<Vector2>();
            enemies = new List<Enemy>();
            bullets = new List<Bullet>();
            score = 0;
            coinsCollectedThisGame = 0;
            enemiesDefeatedThisGame = 0;
            isNewHighScore = false;
            titleTimer = 0f;

            SpawnCoins(10);
            SpawnEnemies(8);
            particleSystem = new ParticleSystem(GraphicsDevice);
        }

        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, tileMap.GetRandomEmptyPosition(rng), tileMap);

            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);

            var fontSystem = new FontSystem();
            fontSystem.AddFont(File.ReadAllBytes("Content/consola.ttf"));
            font = fontSystem.GetFont(18);
            titleFont = fontSystem.GetFont(48);

            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);
                using var shootStream = File.OpenRead("Content/shoot.wav");
                shootSound = SoundEffect.FromStream(shootStream);
            }
            catch { }
        }

        private void SpawnCoins(int count)
        {
            for (int i = 0; i < count; i++)
                coins.Add(tileMap.GetRandomEmptyPosition(rng));
        }

        private void SpawnEnemies(int count)
        {
            for (int i = 0; i < count; i++)
            {
                Vector2 pos = tileMap.GetRandomEmptyPosition(rng);
                EnemyType type = (EnemyType)(rng.Next(3));
                enemies.Add(new Enemy(type, pos, GraphicsDevice));
            }
        }

        protected override void Update(GameTime gameTime)
        {
            float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
            KeyboardState keyboard = Keyboard.GetState();
            currentMouse = Mouse.GetState();
            titleTimer += deltaTime;
            sceneManager.Update();

            switch (sceneManager.CurrentScene)
            {
                case SceneType.Title:
                    if (keyboard.IsKeyDown(Keys.Space))
                    {
                        player = new Player(playerSpriteSheet, tileMap.GetRandomEmptyPosition(rng), tileMap);
                        InitializeGame();
                        sceneManager.GoToGame();
                    }
                    break;

                case SceneType.Game:
                    player.Update(deltaTime);
                    camera.Follow(player.Position);
                    camera.UpdateMatrix();

                    // ★ 发射子弹
                    if (currentMouse.LeftButton == ButtonState.Pressed &&
                        previousMouse.LeftButton == ButtonState.Released)
                    {
                        if (player.TryAttack(deltaTime))
                        {
                            Vector2 mouseWorld = camera.ScreenToWorld(
                                new Vector2(currentMouse.X, currentMouse.Y));
                            Vector2 aimDir = player.GetAimDirection(mouseWorld);
                            bullets.Add(new Bullet(player.Position, aimDir, GraphicsDevice));
                            try { shootSound?.Play(); } catch { }
                        }
                    }

                    // 更新敌人
                    foreach (Enemy enemy in enemies)
                        enemy.Update(deltaTime, player.Position, tileMap);

                    // 更新子弹
                    foreach (Bullet bullet in bullets)
                        bullet.Update(deltaTime, tileMap);

                    // ★ 检测子弹和敌人碰撞
                    CheckBulletEnemyCollision();

                    // 检测玩家和敌人碰撞(不再扣血,只推开?或者保留扣血)
                    CheckEnemyCollision();

                    CheckCoinCollision();

                    // 清理死亡敌人和子弹
                    enemies.RemoveAll(e => !e.IsAlive);
                    bullets.RemoveAll(b => !b.IsAlive);

                    if (coins.Count == 0) SpawnCoins(10);
                    if (enemies.Count == 0) SpawnEnemies(8);

                    particleSystem.Update(deltaTime);

                    if (player.Hp <= 0)
                    {
                        if (score > saveData.HighScore)
                        {
                            saveData.HighScore = score;
                            isNewHighScore = true;
                        }
                        saveData.TotalCoinsCollected += coinsCollectedThisGame;
                        saveData.TotalEnemiesDefeated += enemiesDefeatedThisGame;
                        SaveManager.Save(saveData);
                        sceneManager.GoToGameOver();
                    }
                    break;

                case SceneType.GameOver:
                    break;
            }

            previousMouse = currentMouse;

            if (keyboard.IsKeyDown(Keys.M))
                SoundEffect.MasterVolume = SoundEffect.MasterVolume > 0 ? 0f : 1f;

            if (keyboard.IsKeyDown(Keys.Escape))
                Exit();

            base.Update(gameTime);
        }

        private void CheckBulletEnemyCollision()
        {
            for (int i = bullets.Count - 1; i >= 0; i--)
            {
                for (int j = enemies.Count - 1; j >= 0; j--)
                {
                    if (bullets[i].GetBounds().Intersects(enemies[j].GetBounds()))
                    {
                        enemies[j].Hp -= bullets[i].Damage;
                        bullets[i].IsAlive = false;

                        if (enemies[j].Hp <= 0)
                        {
                            enemies[j].IsAlive = false;
                            score += enemies[j].ScoreValue;
                            enemiesDefeatedThisGame++;
                            particleSystem.EmitHitParticles(enemies[j].Position);
                        }
                        break;
                    }
                }
            }
        }

        private void CheckEnemyCollision()
        {
            Rectangle playerRect = player.GetBounds();
            for (int i = enemies.Count - 1; i >= 0; i--)
            {
                if (enemies[i].GetBounds().Intersects(playerRect))
                {
                    player.Hp -= enemies[i].Attack / 2; // 碰撞伤害减半
                    // 把敌人推开
                    Vector2 pushDir = enemies[i].Position - player.Position;
                    if (pushDir.Length() > 0)
                    {
                        pushDir.Normalize();
                        enemies[i].Position += pushDir * 30;
                    }
                }
            }
        }

        private void CheckCoinCollision()
        {
            Rectangle playerRect = player.GetBounds();
            for (int i = coins.Count - 1; i >= 0; i--)
            {
                Rectangle coinRect = new Rectangle((int)coins[i].X - 12, (int)coins[i].Y - 12, 24, 24);
                if (playerRect.Intersects(coinRect))
                {
                    Vector2 coinPos = coins[i];
                    coins.RemoveAt(i);
                    score += 10;
                    coinsCollectedThisGame++;
                    particleSystem.EmitCoinParticles(coinPos);
                    try { coinSound?.Play(); } catch { }
                }
            }
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            switch (sceneManager.CurrentScene)
            {
                case SceneType.Title:
                    _spriteBatch.Begin();
                    DrawTitle();
                    _spriteBatch.End();
                    break;

                case SceneType.Game:
                    _spriteBatch.Begin(transformMatrix: camera.Transform);
                    DrawGameWorld();
                    _spriteBatch.End();

                    // UI 层
                    _spriteBatch.Begin();
                    DrawGameUI();
                    // ★ 画准星线
                    DrawCrosshair();
                    _spriteBatch.End();
                    break;

                case SceneType.GameOver:
                    _spriteBatch.Begin();
                    DrawGameOver();
                    _spriteBatch.End();
                    break;
            }

            base.Draw(gameTime);
        }

        private void DrawTitle()
        {
            string title = "史莱姆猎人";
            Vector2 titleSize = titleFont.MeasureString(title);
            float alpha = (float)(Math.Sin(titleTimer * 3) + 1) / 2 * 0.5f + 0.5f;
            _spriteBatch.DrawString(titleFont, title,
                new Vector2(400 - titleSize.X / 2, 120), Color.Gold * alpha);
            _spriteBatch.DrawString(font, "收集金币,消灭史莱姆!",
                new Vector2(400 - font.MeasureString("收集金币,消灭史莱姆!").X / 2, 200), Color.LightGray);
            _spriteBatch.DrawString(font, $"🏆 最高分:{saveData.HighScore}", new Vector2(300, 260), Color.Gold);
            _spriteBatch.DrawString(font, $"💰 累计金币:{saveData.TotalCoinsCollected}", new Vector2(300, 285), Color.Yellow);
            _spriteBatch.DrawString(font, $"💀 累计击杀:{saveData.TotalEnemiesDefeated}", new Vector2(300, 310), Color.Red);
            string prompt = "按 空格键 开始游戏 | 鼠标左键射击";
            _spriteBatch.DrawString(font, prompt,
                new Vector2(400 - font.MeasureString(prompt).X / 2, 400), Color.White);
        }

        private void DrawGameWorld()
        {
            tileMap.Draw(_spriteBatch);

            foreach (Vector2 coinPos in coins)
                _spriteBatch.Draw(coinTexture, coinPos, null, Color.White,
                    0f, new Vector2(12, 12), 1f, SpriteEffects.None, 0f);

            foreach (Enemy enemy in enemies)
                enemy.Draw(_spriteBatch);

            // ★ 画子弹
            foreach (Bullet bullet in bullets)
                bullet.Draw(_spriteBatch);

            particleSystem.Draw(_spriteBatch);
            player.Draw(_spriteBatch);
        }

        private void DrawGameUI()
        {
            _spriteBatch.DrawString(font, $"分数:{score}", new Vector2(10, 10), Color.White);
            _spriteBatch.DrawString(font, $"最高分:{saveData.HighScore}", new Vector2(10, 35), Color.Gold);
            _spriteBatch.DrawString(font, $"HP:{player.Hp}/{player.MaxHp}", new Vector2(10, 60), Color.LimeGreen);
            _spriteBatch.DrawString(font, $"敌人:{enemies.Count} | 子弹:{bullets.Count}",
                new Vector2(10, 85), Color.Yellow);
            _spriteBatch.DrawString(font, "WASD移动 | 鼠标瞄准左键射击 | M静音",
                new Vector2(10, 570), Color.LightGray);
        }

        // ★ 画玩家到鼠标的瞄准线
        private void DrawCrosshair()
        {
            Vector2 playerScreen = camera.WorldToScreen(player.Position);
            Vector2 mouseScreen = new Vector2(currentMouse.X, currentMouse.Y);

            // 画一条短线表示瞄准方向
            Vector2 dir = mouseScreen - playerScreen;
            if (dir.Length() > 0)
                dir.Normalize();
            Vector2 lineEnd = playerScreen + dir * 30;

            // 用简单的点来画线
            for (int i = 0; i < 30; i += 2)
            {
                Vector2 dot = playerScreen + dir * i;
                Texture2D dotTex = new Texture2D(GraphicsDevice, 2, 2);
                dotTex.SetData(new[] { Color.White * 0.5f });
                _spriteBatch.Draw(dotTex, dot, Color.White);
            }
        }

        private void DrawGameOver()
        {
            string title = "游戏结束";
            _spriteBatch.DrawString(titleFont, title,
                new Vector2(400 - titleFont.MeasureString(title).X / 2, 100), Color.Red);
            _spriteBatch.DrawString(font, $"本局分数:{score}",
                new Vector2(400 - font.MeasureString($"本局分数:{score}").X / 2, 190), Color.White);
            _spriteBatch.DrawString(font, $"🏆 最高分:{saveData.HighScore}",
                new Vector2(400 - font.MeasureString($"🏆 最高分:{saveData.HighScore}").X / 2, 220), Color.Gold);
            if (isNewHighScore)
                _spriteBatch.DrawString(font, "🎉 新纪录!",
                    new Vector2(400 - font.MeasureString("🎉 新纪录!").X / 2, 255), Color.Orange);
            string prompt = "按 空格键 返回标题画面";
            _spriteBatch.DrawString(font, prompt,
                new Vector2(400 - font.MeasureString(prompt).X / 2, 400), Color.White);
        }
    }
}

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

相关推荐
一口吃俩胖子1 小时前
【脉宽调制DCDC功率变换学习笔记022】DCDC变换器的稳定性、奈奎斯特准则、增益裕度和相位裕度
笔记·学习
weixin_428005301 小时前
C#调用 AI学习从0开始-第1阶段(基础与工具)-第7天多轮对话记忆
人工智能·学习·c#·多轮对话·千问api调用
oddsand11 小时前
AI应用开发学习步骤-java
java·人工智能·学习
知识分享小能手1 小时前
Flask入门学习教程,从入门到精通,Flask智能租房——列表页 知识点详解(7)
python·学习·flask
Raink老师1 小时前
【AI面试临阵磨枪-75】游戏 AI Agent:NPC、剧情生成、攻略助手、社区问答、黑话适配
人工智能·游戏·面试
吃好睡好便好2 小时前
提取矩阵所有元素
开发语言·学习·线性代数·matlab·矩阵
吃好睡好便好2 小时前
提取矩阵特定多列元素
开发语言·学习·线性代数·matlab·矩阵
z200509302 小时前
【Linux学习】Linux中进程终止和进程等待
linux·学习·操作系统
z落落2 小时前
C# 数组属性和方法(Clear / Copy / IndexOf / LastIndexOf)
开发语言·javascript·c#