从零开发游戏需要学习的c#模块,第二十四章(场景管理 —— 标题、游戏、结束画面)

本节课学习目标

  1. 创建三个场景:标题画面、游戏画面、结束画面

  2. 标题画面按空格开始游戏

  3. 玩家死亡后切换到结束画面

  4. 结束画面显示分数,按空格返回标题


第一步:创建场景枚举和场景管理器

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

using Microsoft.Xna.Framework;

using Microsoft.Xna.Framework.Graphics;

using Microsoft.Xna.Framework.Input;

using FontStashSharp;

namespace MY_FIRST_GAME

{

public enum SceneType

{

Title, // 标题画面

Game, // 游戏画面

GameOver // 结束画面

}

public class SceneManager

{

public SceneType CurrentScene { get; private set; } = SceneType.Title;

private KeyboardState previousKeyboard;

public SceneManager()

{

previousKeyboard = Keyboard.GetState();

}

// 检查按键是否刚刚按下(不是按住)

private bool IsKeyJustPressed(Keys key)

{

KeyboardState current = Keyboard.GetState();

bool pressed = current.IsKeyDown(key) && previousKeyboard.IsKeyUp(key);

previousKeyboard = current;

return pressed;

}

public void Update()

{

switch (CurrentScene)

{

case SceneType.Title:

if (IsKeyJustPressed(Keys.Space))

CurrentScene = SceneType.Game;

break;

case SceneType.Game:

// 游戏中不切换场景,由外部调用 GoToGameOver

break;

case SceneType.GameOver:

if (IsKeyJustPressed(Keys.Space))

CurrentScene = SceneType.Title;

break;

}

// 更新键盘状态(如果不是按键检测的话)

previousKeyboard = Keyboard.GetState();

}

public void GoToGameOver()

{

CurrentScene = SceneType.GameOver;

}

public void GoToGame()

{

CurrentScene = SceneType.Game;

}

}

}

第二步:改造 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 Player player = default!;
        private Texture2D playerSpriteSheet;

        // 金币
        private Texture2D coinTexture;
        private List<Vector2> coins = default!;
        private Random rng = default!;
        private int score;

        // 敌人
        private Texture2D enemyTexture;
        private List<Vector2> enemies = default!;

        // 字体
        private SpriteFontBase font = default!;
        private SpriteFontBase titleFont = default!;

        // 音效
        private SoundEffect coinSound = default!;
        private SoundEffect hitSound = default!;

        // 粒子
        private ParticleSystem particleSystem = default!;

        // 标题动画
        private float titleTimer;

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

            base.Initialize();
        }

        // ★ 初始化/重置游戏数据
        private void InitializeGame()
        {
            coins = new List<Vector2>();
            enemies = new List<Vector2>();
            score = 0;
            titleTimer = 0f;

            SpawnCoins(5);
            SpawnEnemies(3);
            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, 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);
            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);
            }
            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();

            titleTimer += deltaTime;

            // ★ 场景管理器更新
            sceneManager.Update();

            switch (sceneManager.CurrentScene)
            {
                case SceneType.Title:
                    UpdateTitle(deltaTime);
                    break;

                case SceneType.Game:
                    UpdateGame(deltaTime, keyboard);
                    break;

                case SceneType.GameOver:
                    UpdateGameOver(deltaTime);
                    break;
            }

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

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

            base.Update(gameTime);
        }

        private void UpdateTitle(float deltaTime) { }

        private void UpdateGame(float deltaTime, KeyboardState keyboard)
        {
            player.Update(deltaTime);
            CheckCoinCollision();
            CheckEnemyCollision();

            if (coins.Count == 0) SpawnCoins(5);
            if (enemies.Count == 0) SpawnEnemies(3);

            particleSystem.Update(deltaTime);

            // ★ 玩家死亡 → 切换场景
            if (player.Hp <= 0)
            {
                sceneManager.GoToGameOver();
            }
        }

        private void UpdateGameOver(float deltaTime) { }

        // ★ 当从标题进入游戏时,重置所有数据
        private void StartNewGame()
        {
            player = new Player(playerSpriteSheet, new Vector2(400, 300));
            InitializeGame();
            sceneManager.GoToGame();
        }

        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;
                    player.Hp -= 15;  // ★ 碰敌人扣血
                    particleSystem.EmitHitParticles(enemyPos);
                    try { hitSound?.Play(); } catch { }
                }
            }
        }

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

            _spriteBatch.Begin();

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

                case SceneType.Game:
                    DrawGame();
                    break;

                case SceneType.GameOver:
                    DrawGameOver();
                    break;
            }

            _spriteBatch.End();
            base.Draw(gameTime);
        }

        // ★ 画标题画面
        private void DrawTitle()
        {
            string title = "控制台 RPG 2D";
            Vector2 titleSize = titleFont.MeasureString(title);
            Vector2 titlePos = new Vector2(400 - titleSize.X / 2, 150);

            // 标题(带闪烁效果)
            float alpha = (float)(Math.Sin(titleTimer * 3) + 1) / 2 * 0.5f + 0.5f;
            _spriteBatch.DrawString(titleFont, title, titlePos, Color.Gold * alpha);

            // 副标题
            string subtitle = "一个 MonoGame 冒险";
            Vector2 subSize = font.MeasureString(subtitle);
            _spriteBatch.DrawString(font, subtitle,
                new Vector2(400 - subSize.X / 2, 230), Color.LightGray);

            // 操作提示
            string prompt = "按 空格键 开始游戏";
            Vector2 promptSize = font.MeasureString(prompt);
            _spriteBatch.DrawString(font, prompt,
                new Vector2(400 - promptSize.X / 2, 350), Color.White);

            // 移动的装饰
            for (int i = 0; i < 10; i++)
            {
                float x = (float)(Math.Sin(titleTimer * 2 + i * 0.7) * 300 + 400);
                float y = 420 + i * 15;
                _spriteBatch.Draw(coinTexture, new Vector2(x, y),
                    Color.White * 0.3f);
            }
        }

        // ★ 画游戏画面
        private void DrawGame()
        {
            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, $"HP:{player.Hp}/{player.MaxHp}",
                new Vector2(10, 85), Color.LimeGreen);
            _spriteBatch.DrawString(font, "WASD移动 | M静音 | ESC退出",
                new Vector2(10, 570), Color.LightGray);
        }

        // ★ 画结束画面
        private void DrawGameOver()
        {
            string title = "游戏结束";
            Vector2 titleSize = titleFont.MeasureString(title);
            _spriteBatch.DrawString(titleFont, title,
                new Vector2(400 - titleSize.X / 2, 150), Color.Red);

            string scoreText = $"最终分数:{score}";
            Vector2 scoreSize = font.MeasureString(scoreText);
            _spriteBatch.DrawString(font, scoreText,
                new Vector2(400 - scoreSize.X / 2, 250), Color.Gold);

            string prompt = "按 空格键 返回标题画面";
            Vector2 promptSize = font.MeasureString(prompt);
            _spriteBatch.DrawString(font, prompt,
                new Vector2(400 - promptSize.X / 2, 350), Color.White);
        }

        // ★ 重置游戏(从标题画面进入时调用)
        // 注意:这个方法通过 SceneManager 的状态切换来触发
        private SceneType _previousScene = SceneType.Title;
    }
}

今天学习的内容到此结束,我是魔法阵维护师,关注我,下期更精彩!

相关推荐
wuxinyan12310 小时前
工业级大模型学习之路025:问题解决-检索质量全为0
人工智能·python·学习·langchain
happymaker062610 小时前
SpringBoot学习日记——DAY05(SpringBoot整合MyBatis-plus实现增删改查)
spring boot·学习·mybatis
吃好睡好便好10 小时前
用直接输入的方式创建矩阵
开发语言·人工智能·学习·线性代数·算法·matlab·矩阵
唐青枫10 小时前
别把登录写散了:C#.NET IdentityServer4 统一认证与 JWT 授权实战
c#·.net
秋雨梧桐叶落莳10 小时前
iOS——UIStackView学习
学习·macos·ios·objective-c·cocoa
z2005093011 小时前
今日算法(二叉搜索树)
学习·leetcode
神谕的祝福11 小时前
comfyui从0到1开始学习-第二讲参数实验
学习
魔法阵维护师11 小时前
从零开发游戏需要学习的c#模块,第二十三章(存档与高分系统)
学习·游戏·c#
加号318 小时前
【C#】 字符串字节到十六进制字节数组的转换解析
c#