当前位置: 首页 > news >正文

从零开发游戏需要学习的c#模块,第二十一章(精灵动画 —— 让角色走起来)

今天我们要学习的内容是理解精灵图集的原理加载精灵图集并切帧实现四方向行走动画静止时显示待机帧第一步准备精灵图集精灵图集就是一张大图里包含多个小图帧播放时依次显示每一帧形成动画效果。一个简单的 4 帧行走图横向排列 [帧1][帧2][帧3][帧4]推荐资源网站免费itch.io 搜 top-down character sprite 或 rpg character sprite sheet下载任意一个角色精灵图集放到Content文件夹设为“如果较新则复制”。第二步创建动画管理器右键项目 →添加→类文件名Animation.csusing Microsoft.Xna.Framework;using Microsoft.Xna.Framework.Graphics;namespace MY_FIRST_GAME{public class Animation{private Texture2D texture; // 精灵图集private int frameCount; // 总帧数private int currentFrame; // 当前帧private float frameWidth; // 每帧宽度private float frameTime; // 每帧显示时间秒private float timer; // 计时器private bool isPlaying; // 是否播放中private bool isLooping; // 是否循环public Animation(Texture2D texture, int frameCount, float frameTime, bool looping true){this.texture texture;this.frameCount frameCount;this.frameTime frameTime;this.isLooping looping;frameWidth texture.Width / frameCount;currentFrame 0;timer 0f;isPlaying true;}public void Update(float deltaTime){if (!isPlaying) return;timer deltaTime;if (timer frameTime){timer 0f;currentFrame;if (currentFrame frameCount){if (isLooping)currentFrame 0; // 循环播放else{currentFrame frameCount - 1; // 停在最后一帧isPlaying false;}}}}// 获取当前帧的源矩形public Rectangle GetSourceRectangle(){return new Rectangle(currentFrame * (int)frameWidth,0,(int)frameWidth,texture.Height);}public void Play(){isPlaying true;}public void Stop(){isPlaying false;}public void Reset(){currentFrame 0;timer 0f;isPlaying true;}public int CurrentFrame currentFrame;public int FrameCount frameCount;public float FrameWidth frameWidth;public Texture2D Texture texture;}}第三步创建玩家类右键项目 →添加→类文件名Player.csusing Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace MY_FIRST_GAME { public class Player { public Vector2 Position { get; set; } public float Speed { get; set; } 200f; public int Hp { get; set; } 100; public int MaxHp { get; set; } 100; public int Attack { get; set; } 15; private Texture2D texture; private Animation? currentAnimation; private Animation idleAnimation; private Animation walkAnimation; private bool isMoving; private Vector2 lastPosition; public Player(Texture2D spriteSheet, Vector2 startPosition) { Position startPosition; texture spriteSheet; // 假设精灵图集有4帧每帧0.15秒 // 如果你的图集帧数不同改这两个数字 idleAnimation new Animation(texture, 1, 0f, true); // 待机用第一帧 walkAnimation new Animation(texture, 4, 0.15f, true); // 行走4帧 currentAnimation idleAnimation; } public void Update(float deltaTime) { KeyboardState keyboard Keyboard.GetState(); lastPosition Position; isMoving false; if (keyboard.IsKeyDown(Keys.W) || keyboard.IsKeyDown(Keys.Up)) { Position.Y - Speed * deltaTime; isMoving true; } if (keyboard.IsKeyDown(Keys.S) || keyboard.IsKeyDown(Keys.Down)) { Position.Y Speed * deltaTime; isMoving true; } if (keyboard.IsKeyDown(Keys.A) || keyboard.IsKeyDown(Keys.Left)) { Position.X - Speed * deltaTime; isMoving true; } if (keyboard.IsKeyDown(Keys.D) || keyboard.IsKeyDown(Keys.Right)) { Position.X Speed * deltaTime; isMoving true; } // 限制边界 Position new Vector2( Math.Clamp(Position.X, 32, 768), Math.Clamp(Position.Y, 32, 568) ); // 切换动画 if (isMoving) { if (currentAnimation ! walkAnimation) { walkAnimation.Reset(); currentAnimation walkAnimation; } } else { currentAnimation idleAnimation; } currentAnimation.Update(deltaTime); } public void Draw(SpriteBatch spriteBatch) { Rectangle sourceRect currentAnimation.GetSourceRectangle(); Vector2 origin new Vector2(sourceRect.Width / 2, sourceRect.Height / 2); spriteBatch.Draw( texture, Position, sourceRect, Color.White, 0f, origin, 1f, SpriteEffects.None, 0f ); } public Rectangle GetBounds() { Rectangle sourceRect currentAnimation.GetSourceRectangle(); return new Rectangle( (int)(Position.X - sourceRect.Width / 2), (int)(Position.Y - sourceRect.Height / 2), sourceRect.Width, sourceRect.Height ); } } }第四步简化版 Game1.cs把Game1.cs完整替换为using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; using System.Collections.Generic; using System.IO; using FontStashSharp; namespace MY_FIRST_GAME { public enum GameState { Exploring, Battling } public class Game1 : Game { private GraphicsDeviceManager _graphics; private SpriteBatch _spriteBatch; private Player player default!; private Texture2D playerSpriteSheet; private Texture2D coinTexture; private ListVector2 coins; private Random rng; private int score; private Texture2D enemyTexture; private ListVector2 enemies; private SpriteFontBase font; private GameState state GameState.Exploring; public Game1() { _graphics new GraphicsDeviceManager(this); Content.RootDirectory Content; IsMouseVisible true; } protected override void Initialize() { _graphics.PreferredBackBufferWidth 800; _graphics.PreferredBackBufferHeight 600; _graphics.ApplyChanges(); rng new Random(); coins new ListVector2(); enemies new ListVector2(); score 0; SpawnCoins(5); SpawnEnemies(3); base.Initialize(); } 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); } 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(); if (state GameState.Exploring) { player.Update(deltaTime); CheckCoinCollision(); CheckEnemyCollision(); if (coins.Count 0) SpawnCoins(5); if (enemies.Count 0) SpawnEnemies(3); } 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)) { coins.RemoveAt(i); score 10; } } } 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)) { enemies.RemoveAt(i); score 50; } } } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); _spriteBatch.Begin(); // 金币 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); // ★ 玩家使用动画系统 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移动 | ESC退出, new Vector2(10, 570), Color.LightGray); _spriteBatch.End(); base.Draw(gameTime); } } }今天的学习就此结束我叫魔法阵维护师关注我下期更精彩
http://www.zskr.cn/news/1350868.html

相关文章:

  • 并发编程学习-Atomic体系和Collection
  • 【2026年AI工具市场终极预判】:基于37家头部厂商财报、127项技术指标与Gartner/IDC交叉验证的格局演进图谱
  • 黎曼猜想:哲学 × 数学 思维范式全链条
  • P vs NP:西方哲学 × 西方计算理论 —— 人类思维的终极边界
  • 【代码辅助】Cursor vs GitHub Copilot:哪款才是测试开发工程师的最强IDE?
  • 深入理解react-tween-state的动画堆叠行为:ADDITIVE vs DESTRUCTIVE的完整对比
  • HS2-HF_Patch:Honey Select 2 终极汉化与功能增强完整指南
  • 如何选择深度学习数据集?Awesome Deep Learning Resources 实用资源解析
  • 【收藏干货】2026年AI Coding全面爆发!程序员终极职业升级攻略,告别被替代焦虑
  • CANN/pypto copysign函数API文档
  • app应用接入广告的完整流程和方法:从零搭建可持续变现体系
  • 2026年5月最新贵阳息烽黄金回收白银回收铂金回收权威排行榜TOP5:纯金+金条+银条+钯金 门店地址联系方式推荐 - 金诚回收
  • cpulimit进程组管理终极指南:如何优雅控制父子进程的CPU资源分配
  • Go语言六边形架构:端口与适配器
  • 10个sd-webui-regional-prompter实用技巧:从基础分割到高级2D区域配置
  • 2026年5月最新齐齐哈尔泰来黄金回收白银回收铂金回收权威排行榜TOP5:纯金+金条+银条+钯金 门店地址联系方式推荐 - 诚信金利回收
  • 2026 年 GEO 行业大洗牌:90% SEO 公司将被淘汰,真正的机会在这里 - 商业科技观察
  • MySQL高频面试题-02
  • CANN/asc-devkit浮点转hif8 API
  • Jooby性能优化秘籍:让你的Web应用快如闪电 [特殊字符]
  • CANN/asc-devkit浮点到FP8转换API
  • 2026年10款降AI率工具实测:最高AI率100%直降至0.12%
  • 一家工厂的“打样能力“怎么从外部判断?一份给跨境卖家与新品牌的甄别清单
  • Solaar 4.0:解锁罗技设备的完整Linux管理体验
  • 互联网大厂 Java 求职面试实战:音视频场景中的技术挑战
  • 铜钟音乐:如何用React技术栈构建纯净无干扰的现代音乐播放平台?
  • 【软考网络工程师-案例分析易错题整理(下)】
  • Java对象内存布局与对齐填充
  • 2026年5月最新泉州石狮黄金回收白银回收铂金回收权威排行榜TOP5:纯金+金条+银条+钯金 门店地址联系方式推荐 - 诚信金利回收
  • 2026年5月最新福州连江黄金回收白银回收铂金回收权威排行榜TOP5:纯金+金条+银条+钯金 门店地址联系方式推荐 - 金诚回收