从零开发游戏需要学习的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.coloradmin.cn/o/2636142.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!