从零开发游戏需要学习的c#模块,第二十三章(存档与高分系统)

本节课学习目标

  1. 用 JSON 保存最高分到文件

  2. 游戏结束时自动更新最高分

  3. 标题画面显示最高分

  4. 结束画面显示当前分数和最高分


第一步:创建存档管理类

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

using System.IO;

using System.Text.Json;

namespace MY_FIRST_GAME

{

public class SaveData

{

public int HighScore { get; set; }

public int TotalCoinsCollected { get; set; }

public int TotalEnemiesDefeated { get; set; }

}

public static class SaveManager

{

private static string filePath = "savedata.json";

public static SaveData Load()

{

if (File.Exists(filePath))

{

string json = File.ReadAllText(filePath);

SaveData? data = JsonSerializer.Deserialize<SaveData>(json);

return data ?? new SaveData();

}

return new SaveData();

}

public static void Save(SaveData data)

{

string json = JsonSerializer.Serialize(data);

File.WriteAllText(filePath, json);

}

}

}

第二步:完整替换 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 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;

// ★ 存档数据
private SaveData saveData = default!;
private int coinsCollectedThisGame;
private int enemiesDefeatedThisGame;
private bool isNewHighScore;

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

InitializeGame();

base.Initialize();
}

cs 复制代码
        private void InitializeGame()
        {
            coins = new List<Vector2>();
            enemies = new List<Vector2>();
            score = 0;
            coinsCollectedThisGame = 0;
            enemiesDefeatedThisGame = 0;
            isNewHighScore = false;
            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:
                    if (Keyboard.GetState().IsKeyDown(Keys.Space) && sceneManager.CurrentScene == SceneType.Title)
                    {
                        player = new Player(playerSpriteSheet, new Vector2(400, 300));
                        InitializeGame();
                        sceneManager.GoToGame();
                    }
                    break;

                case SceneType.Game:
                    player.Update(deltaTime);
                    CheckCoinCollision();
                    CheckEnemyCollision();
                    if (coins.Count == 0) SpawnCoins(5);
                    if (enemies.Count == 0) SpawnEnemies(3);
                    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;
            }

            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;
                    coinsCollectedThisGame++;
                    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;
                    enemiesDefeatedThisGame++;
                    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 = "史莱姆猎人";
            Vector2 titleSize = titleFont.MeasureString(title);
            Vector2 titlePos = new Vector2(400 - titleSize.X / 2, 120);

            float alpha = (float)(Math.Sin(titleTimer * 3) + 1) / 2 * 0.5f + 0.5f;
            _spriteBatch.DrawString(titleFont, title, titlePos, Color.Gold * alpha);

            string subtitle = "收集金币,消灭史莱姆!";
            Vector2 subSize = font.MeasureString(subtitle);
            _spriteBatch.DrawString(font, subtitle,
                new Vector2(400 - subSize.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 = "按 空格键 开始游戏";
            Vector2 promptSize = font.MeasureString(prompt);
            _spriteBatch.DrawString(font, prompt,
                new Vector2(400 - promptSize.X / 2, 400), Color.White);
        }

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

            _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, $"本局金币:{coinsCollectedThisGame}",
                new Vector2(10, 85), Color.Yellow);
            _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, 100), Color.Red);

            string scoreText = $"本局分数:{score}";
            Vector2 scoreSize = font.MeasureString(scoreText);
            _spriteBatch.DrawString(font, scoreText,
                new Vector2(400 - scoreSize.X / 2, 190), Color.White);

            string highScoreText = $"🏆 最高分:{saveData.HighScore}";
            Vector2 hsSize = font.MeasureString(highScoreText);
            _spriteBatch.DrawString(font, highScoreText,
                new Vector2(400 - hsSize.X / 2, 220), Color.Gold);

            // ★ 新纪录提示
            if (isNewHighScore)
            {
                string newRecord = "🎉 新纪录!";
                Vector2 nrSize = font.MeasureString(newRecord);
                _spriteBatch.DrawString(font, newRecord,
                    new Vector2(400 - nrSize.X / 2, 255), Color.Orange);
            }

            string coinsText = $"本局收集金币:{coinsCollectedThisGame}";
            Vector2 cSize = font.MeasureString(coinsText);
            _spriteBatch.DrawString(font, coinsText,
                new Vector2(400 - cSize.X / 2, 290), Color.Yellow);

            string enemiesText = $"本局消灭敌人:{enemiesDefeatedThisGame}";
            Vector2 eSize = font.MeasureString(enemiesText);
            _spriteBatch.DrawString(font, enemiesText,
                new Vector2(400 - eSize.X / 2, 315), Color.Red);

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

现在的游戏流程

text

复制代码
标题画面(显示最高分、累计统计)
    ↓ 按空格
游戏画面(实时显示当前分数 vs 最高分)
    ↓ HP归零
结束画面(显示本局成绩、是否新纪录、最高分)
    ↓ 按空格
返回标题画面

存档文件 savedata.json 保存在游戏目录下,关掉重开最高分还在。

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

相关推荐
开维游戏引擎3 小时前
AI自动生成游戏时,deepseek和mimo对比
android·游戏·语言模型·游戏引擎·ai编程
加号34 小时前
【C#】 文件与目录管理:创建、删除操作的技术解析
开发语言·c#
海兰8 小时前
【游戏】迷雾镇(Mist Town)AI 沙箱游戏详细设计与部署指南(附源代码)
人工智能·游戏
千寻girling8 小时前
记录第一次学习 Docker
学习·docker·容器
用户395240998808 小时前
SqlSugar 连接 PostgreSQL 报错 42P01: relation does not exist 的排查与修复
c#
Kobebryant-Manba9 小时前
学习RNN(简洁实现)
人工智能·rnn·学习
2601_961875249 小时前
法考考试时间安排及科目|时间表|资料已整理
开发语言·c#·inverted-index·suffix-tree·sstable·r-tree·lsm-tree
ServBay9 小时前
你跟高级 C# 工程师的区别,就是这8个开发技巧
后端·c#·.net
知南x9 小时前
【DPDK例程学习】(4) l2fwd
学习·word
努力努力再努力FFF9 小时前
大学四年AI能力规划:从入门学习到简历表达
人工智能·学习