从零开发游戏需要学习的c#模块,第二十三章(存档与高分系统)
本节课学习目标
用 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(); } 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); } } }本节课的学习到此结束,我是魔法阵维护师,关注我,下期更精彩!
