webabcd - 专注于asp.net, html5, silverlight

ASP.NET
从现在开始 一切都不晚
posts - 290, comments - 7901, trackbacks - 594, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

梦想成真 XNA (7) - 碰撞检测

Posted on 2011-08-15 10:07 webabcd 阅读(2593) 评论(6) 编辑 收藏

[源码下载] 


梦想成真 XNA (7) - 碰撞检测



作者:webabcd


介绍
XNA: 碰撞检测

  • 通过 AABB(Axis Aligned Bounding Box)实现碰撞检测算法
  • 通过 Rectangle 类实现碰撞检测算法



示例
1、AABB 算法的 Demo(按键盘 N 键加载此 Demo)
Component/Collision/AABB.cs

/*
 * AABB - Axis Aligned Bounding Box
 * 所谓的坐标轴对齐(Axis Aligned),指的是盒体与世界坐标轴平行,同时盒体的每个边(2D)或面(3D)都和一条坐标轴平行(垂直)
 * 
 * 本 Demo 用于演示通过 AABB 算法检测两个精灵是否发生了碰撞
 */

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace XNA.Component.Collision
{
    public class AABB : Microsoft.Xna.Framework.DrawableGameComponent
    {
        // 精灵绘制器
        SpriteBatch _spriteBatch;

        // 精灵对象
        Texture2D _sprite;

        // 精灵的位置(系统控制的)
        Vector2 _systemSpritePosition = new Vector2(600, 300);

        // 精灵的速度(系统控制的),单位:像素数/每帧
        int _systemSpriteSpeed = 6;

        // 精灵的位置(用户控制的)
        Vector2 _userSpritePosition = Vector2.Zero;

        // 精灵的速度(用户控制的),单位:像素数/每帧
        int _userSpriteSpeed = 6;

        // 是否发生了碰撞
        bool _collided = false;

        public AABB(Game game)
            : base(game)
        {

        }

        public override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            _spriteBatch = new SpriteBatch(Game.GraphicsDevice);
            _sprite = Game.Content.Load<Texture2D>("Image/Son");
        }

        public override void Update(GameTime gameTime)
        {
            // 如果系统控制的精灵和用户控制的精灵发生了碰撞,则停止动画
            if (!CheckCollision())
            {
                calcSystemSpritePosition();
                calcUserSpritePosition();
            }
            else
            {
                _collided = true;
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// 检查系统控制的精灵和用户控制的精灵是否发生了碰撞(通过 AABB 算法)
        /// </summary>
        /// <returns>两个精灵是否发生了碰撞</returns>
        private bool CheckCollision()
        {
            /*
             * 盒体与世界坐标轴平行,同时盒体的每个边(2D)或面(3D)都和一条坐标轴平行
             * AABB 包围盒是由一个 Max 坐标和一个 Min 坐标组成的,算法如下
             */

            Vector2 systemSpriteMin = new Vector2(_systemSpritePosition.X, _systemSpritePosition.Y);
            Vector2 systemSpriteMax = new Vector2(_systemSpritePosition.X + _sprite.Width, _systemSpritePosition.Y + _sprite.Height);
            Vector2 userSpriteMin = new Vector2(_userSpritePosition.X, _userSpritePosition.Y);
            Vector2 userSpriteMax = new Vector2(_userSpritePosition.X + _sprite.Width, _userSpritePosition.Y + _sprite.Height);

            if (systemSpriteMin.X <= userSpriteMax.X && systemSpriteMin.Y <= userSpriteMax.Y && systemSpriteMax.X >= userSpriteMin.X && systemSpriteMax.Y >= userSpriteMin.Y)
                return true;

            return false;
        }

        // 计算用户控制的精灵的位置
        private void calcUserSpritePosition()
        {
            KeyboardState keyboardState = Keyboard.GetState();

            if (keyboardState.IsKeyDown(Keys.Left) && _userSpritePosition.X >= 0)
                _userSpritePosition.X -= _userSpriteSpeed;
            if (keyboardState.IsKeyDown(Keys.Right) && _userSpritePosition.X <= Game.Window.ClientBounds.Width - _sprite.Width)
                _userSpritePosition.X += _userSpriteSpeed;
            if (keyboardState.IsKeyDown(Keys.Up) && _userSpritePosition.Y >= 0)
                _userSpritePosition.Y -= _userSpriteSpeed;
            if (keyboardState.IsKeyDown(Keys.Down) && _userSpritePosition.Y <= Game.Window.ClientBounds.Height - _sprite.Height)
                _userSpritePosition.Y += _userSpriteSpeed;

        }

        // 计算系统控制的精灵的位置
        private void calcSystemSpritePosition()
        {
            // 系统控制的精灵在 Y 轴方向上做往复运动
            _systemSpritePosition.Y += _systemSpriteSpeed;

            if (_systemSpritePosition.Y > Game.Window.ClientBounds.Height - _sprite.Height || _systemSpritePosition.Y < 0)
                _systemSpriteSpeed *= -1;
        }

        public override void Draw(GameTime gameTime)
        {
            if (_collided)
                Game.GraphicsDevice.Clear(Color.IndianRed);
            else
                Game.GraphicsDevice.Clear(Color.CornflowerBlue);

            // 在指定的位置上绘制精灵
            _spriteBatch.Begin();
            _spriteBatch.Draw(_sprite, _systemSpritePosition, Color.Red); // 绘制系统控制的精灵
            _spriteBatch.Draw(_sprite, _userSpritePosition, Color.White); // 绘制用户控制的精灵
            _spriteBatch.End();

            base.Update(gameTime);
        }
    }
}



2、通过 Rectangle 检测碰撞的 Demo(按键盘 O 键加载此 Demo)
Component/Collision/RectangleCollision.cs 

/*
 * 本 Demo 用于演示通过 Rectangle 类实现碰撞检测算法
 */

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace XNA.Component.Collision
{
    public class RectangleCollision : Microsoft.Xna.Framework.DrawableGameComponent
    {
        // 精灵绘制器
        SpriteBatch _spriteBatch;

        // 精灵对象
        Texture2D _sprite;

        // 精灵的位置(系统控制的)
        Vector2 _systemSpritePosition = new Vector2(600, 300);

        // 精灵的速度(系统控制的),单位:像素数/每帧
        int _systemSpriteSpeed = 6;

        // 精灵的位置(用户控制的)
        Vector2 _userSpritePosition = Vector2.Zero;

        // 精灵的速度(用户控制的),单位:像素数/每帧
        int _userSpriteSpeed = 6;

        // 是否发生了碰撞
        bool _collided = false;

        public RectangleCollision(Game game)
            : base(game)
        {

        }

        public override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            _spriteBatch = new SpriteBatch(Game.GraphicsDevice);
            _sprite = Game.Content.Load<Texture2D>("Image/Son");
        }

        public override void Update(GameTime gameTime)
        {
            // 如果系统控制的精灵和用户控制的精灵发生了碰撞,则停止动画
            if (!CheckCollision())
            {
                calcSystemSpritePosition();
                calcUserSpritePosition();
            }
            else
            {
                _collided = true;
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// 检查系统控制的精灵和用户控制的精灵是否发生了碰撞(通过 Rectangle 类的帮助)
        /// </summary>
        /// <returns>两个精灵是否发生了碰撞</returns>
        private bool CheckCollision()
        {
            Rectangle systemSpriteRect = new Rectangle((int)_systemSpritePosition.X, (int)_systemSpritePosition.Y, _sprite.Width, _sprite.Height);
            Rectangle userSpriteRect = new Rectangle((int)_userSpritePosition.X, (int)_userSpritePosition.Y, _sprite.Width, _sprite.Height);

            if (systemSpriteRect.Intersects(userSpriteRect))
                return true;

            return false;
        }

        // 计算用户控制的精灵的位置
        private void calcUserSpritePosition()
        {
            KeyboardState keyboardState = Keyboard.GetState();

            if (keyboardState.IsKeyDown(Keys.Left) && _userSpritePosition.X >= 0)
                _userSpritePosition.X -= _userSpriteSpeed;
            if (keyboardState.IsKeyDown(Keys.Right) && _userSpritePosition.X <= Game.Window.ClientBounds.Width - _sprite.Width)
                _userSpritePosition.X += _userSpriteSpeed;
            if (keyboardState.IsKeyDown(Keys.Up) && _userSpritePosition.Y >= 0)
                _userSpritePosition.Y -= _userSpriteSpeed;
            if (keyboardState.IsKeyDown(Keys.Down) && _userSpritePosition.Y <= Game.Window.ClientBounds.Height - _sprite.Height)
                _userSpritePosition.Y += _userSpriteSpeed;

        }

        // 计算系统控制的精灵的位置
        private void calcSystemSpritePosition()
        {
            // 系统控制的精灵在 Y 轴方向上做往复运动
            _systemSpritePosition.Y += _systemSpriteSpeed;

            if (_systemSpritePosition.Y > Game.Window.ClientBounds.Height - _sprite.Height || _systemSpritePosition.Y < 0)
                _systemSpriteSpeed *= -1;
        }

        public override void Draw(GameTime gameTime)
        {
            if (_collided)
                Game.GraphicsDevice.Clear(Color.IndianRed);
            else
                Game.GraphicsDevice.Clear(Color.CornflowerBlue);

            // 在指定的位置上绘制精灵
            _spriteBatch.Begin();
            _spriteBatch.Draw(_sprite, _systemSpritePosition, Color.Red); // 绘制系统控制的精灵
            _spriteBatch.Draw(_sprite, _userSpritePosition, Color.White); // 绘制用户控制的精灵
            _spriteBatch.End();

            base.Update(gameTime);
        }
    }
}



OK 
[源码下载]

Feedback

#1楼  回复 引用 查看   

2011-08-17 11:35 by woiwoqq      
终于又发文了,还是xna啊,还是感觉用不到

#2楼[楼主]  回复 引用 查看   

2011-08-17 17:09 by webabcd      
@woiwoqq
:)
写写小游戏还是挺有趣的

#3楼  回复 引用 查看   

2011-08-18 20:43 by 张磊_larry.zhang      
请问在检测到碰撞之后的处理中,必须判断其方向,使其中一物体往反方向移动一小段距离吗?

#4楼[楼主]  回复 引用 查看   

2011-08-19 09:01 by webabcd      
@张磊_larry.zhang
:)
这要看需求了

#5楼  回复 引用 查看   

2011-08-22 16:45 by 张磊_larry.zhang      
@webabcd
楼主您好,我最近正好碰到一个问题,而且这个问题在您的demo中在同样存在,就是检测到碰撞之后的处理
楼主的demo中,用户用上下左右键操纵A移动,碰撞B后即停止,而用户一般想要的结果是A碰撞到B后,假如用户仍然操纵A往B的方向移动,则A保持原地不动,假如用户操纵A往B的反方向移动,则A又可以移动
对于2D等较简单的情况下,可以判断碰撞后A物体在B物体的方向来手工禁用相对应的上下左右键,但对于较复杂的情况下,比如物体的移动是由手柄,操纵杆等很多按键共同操纵的结果,上述方法就不适用了,请问楼主对这方面有没有更好的思路,我已经被困在这个问题上两三天了,十分感谢!

#6楼[楼主]  回复 引用 查看   

2011-08-22 17:14 by webabcd      
@张磊_larry.zhang
推荐用物理引擎,之前写sl游戏的时候用过一个,也支持xna,如下:http://farseerphysics.codeplex.com/
发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 2138732 tFtJqT/d0Bc=