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

发布时间:2026/5/28 0:22:14

从零开发游戏需要学习的c#模块,第二十七章(远程攻击 —— 发射子弹) 本节课目标按空格键向鼠标方向发射子弹子弹碰到敌人后敌人扣血子弹碰到墙壁后消失敌人被子弹打死才消失不再碰撞扣血第一步创建子弹类右键项目 →添加→类文件名Bullet.cscsharpusing 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里添加// 添加字段 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完整替换为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 ListVector2 coins default!; private Random rng default!; private int score; private ListEnemy 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 ListBullet 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 ListVector2(); enemies new ListEnemy(); bullets new ListBullet(); 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); } } }好了本节课学习到此结束我是魔法阵维护师关注我下期更精彩

相关新闻