本节课学习目标
-
用 JSON 保存最高分到文件
-
游戏结束时自动更新最高分
-
标题画面显示最高分
-
结束画面显示当前分数和最高分
第一步:创建存档管理类
右键项目 → 添加 → 类 ,文件名 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 保存在游戏目录下,关掉重开最高分还在。
本节课的学习到此结束,我是魔法阵维护师,关注我,下期更精彩!