К основному контенту

Farseer Physics (2D физика в XNA)

Сегодня начнем знакомиться с 2d физическим движком, который можно использовать в XNA. http://farseerphysics.codeplex.com . Движок бесплатный, разработка новых версий ведется. Использую версию 2.1.3 (c# 2008 express, XNA 3.1)
Для начала я приведу самый простой код чтобы быстро понять базис и сделать «привет миру».
Создаем проект из шаблона winGame, подключаем библиотеку, которую нужно скачать на сайте разработчика (взять готовую или взять проект и скомпилировать у себя).
Подключил в общие ссылки, потому что хотела XNA 3.0, а у меня 3.1. Но это не страшно, потому что мы только знакомимся, - ошибку можно исправить методами студии и с выходом новой версии для полноценного релиза игры.
Задумка такая: есть 2 объекта один из них статичен, а второй динамичный и управляется нами: Коробка, на столе. Управляем коробкой, а не столом :)

Первое что нам нужно это добавить пространства имен в проект:
using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Interfaces;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Factories;
В свойства основного класса добавляем:
PhysicsSimulator physicsSimulator;
Body boxBody;
Geom boxGeom;
float boxMass=0.1f;
float boxWidth = 100;
float boxHeight = 100;
Texture2D boxTexture;
Body platformBody;
Geom platformGeom;
float platformWidth = 1000;
float platformHeight = 100;
Texture2D platformTexture;
Опишу то что ново PhysicsSimulator это главный класс самого симулятора – без него никак именно сюда все складывается обрабатывается и обновляется. Каждый объект движка должен иметь два класса: свойства самого тела (Body) – вес, инерция, направление силы и т.д.; и его геометрию Geom (координатные характеристики) – для расчета коллизий и взаимодействий с другими объектами.
В конструкторе класса создаем физический симулятор, и задаем ему направление гравитации:
physicsSimulator = new PhysicsSimulator(new Vector2(0, 100));
А в методе обновления добавим обновление симулятора
physicsSimulator.Update(gameTime.ElapsedGameTime.Milliseconds * 0.001f);
Теперь перейдем к методу загрузки данных (контента):
boxBody = BodyFactory.Instance.CreateRectangleBody(physicsSimulator, boxWidth, boxHeight, boxMass);
boxBody.Position = new Vector2(700, 100);
boxGeom = GeomFactory.Instance.CreateRectangleGeom(physicsSimulator, boxBody, boxWidth, boxHeight);
boxTexture = new Texture2D(GraphicsDevice, (int)boxWidth, (int)boxHeight, 1, TextureUsage.None, SurfaceFormat.Color);
Color[] color = new Color[(int)boxWidth * (int)boxHeight]; for (int i = 0; i < color.Length; i++) color[i] = Color.Red;
boxTexture.SetData(color);
platformBody = BodyFactory.Instance.CreateRectangleBody(physicsSimulator, platformWidth, platformHeight, 1);
platformGeom = GeomFactory.Instance.CreateRectangleGeom(physicsSimulator, platformBody, platformWidth, platformHeight);
platformBody.Position = new Vector2(1280/2,720/2);
platformBody.IsStatic = true;
platformTexture = new Texture2D(GraphicsDevice, (int)platformWidth, (int)platformHeight, 1, TextureUsage.None, SurfaceFormat.Color);
Color[] bcolor = new Color[(int)platformWidth * (int)platformHeight]; for (int i = 0; i < bcolor.Length; i++) bcolor[i] = Color.Green;
platformTexture.SetData(bcolor);
С помощью BodyFactory создаем тело с параметрами ширина, высота, масса. Задаем ему начальную позицию. Далее создаем этому телу геометрию, опять же с помощью фабрики, но уже GeomFactory, передаем тело, ширину, высоту. То же самое производим с телом платформой (столом) только после всех действий указываем, что оно статично: platformBody.IsStatic = true. Текстуры я не готовил специально – потому, что так код становиться более универсальный, и самое главное, что так виднее узреть само взаимодействие. Для создания монотонной текстуры средствами кода я использовал:
boxTexture = new Texture2D(GraphicsDevice, (int)boxWidth, (int)boxHeight, 1, TextureUsage.None, SurfaceFormat.Color);
Color[] color = new Color[(int)boxWidth * (int)boxHeight]; for (int i = 0; i < color.Length; i++) color[i] = Color.Red;
boxTexture.SetData(color);
Тут нечего описывать: создается пустая текстура и попиксельно собираться цвет для ее закрашивания. Чтобы управлять объектом - добавляем код в метод обновления:
if (keyboardState.IsKeyDown(Keys.Up)) boxBody.ApplyForce(new Vector2(0, -100));
if (keyboardState.IsKeyDown(Keys.Down)) boxBody.ApplyForce(new Vector2(0, 100));
if (keyboardState.IsKeyDown(Keys.Left)) boxBody.ApplyForce(new Vector2(-100, 0));
if (keyboardState.IsKeyDown(Keys.Right)) boxBody.ApplyForce(new Vector2(100, 0));
В соответствии одной из четырех клавиш (вверх, вниз, влево, вправо) применяем вектор силы методом ApplyForce. Часто нужно остановить тело внезапно, я не нашел встроенного метода чтобы реализовать это нужно использовать такую формулу (по нажатию на пробел тело останавливается, но не перестает крутиться):
if (keyboardState.IsKeyDown(Keys.Space)) boxBody.ApplyImpulse(boxBody.LinearVelocity * -boxBody.Mass);
Полный текст кода
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;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Interfaces;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Factories;

namespace FarseerEx1
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        
        PhysicsSimulator physicsSimulator;
        Body boxBody;
        Geom boxGeom;
        float boxMass=0.1f;
        float boxWidth = 100;
        float boxHeight = 100;
        Texture2D boxTexture;
        Body platformBody;
        Geom platformGeom;
        float platformWidth = 1000;
        float platformHeight = 100;
        Texture2D platformTexture;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 720;
            physicsSimulator = new PhysicsSimulator(new Vector2(0, 100));
        }

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

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            boxBody = BodyFactory.Instance.CreateRectangleBody(physicsSimulator, boxWidth, boxHeight, boxMass);
            boxBody.Position = new Vector2(700, 100);
            boxGeom = GeomFactory.Instance.CreateRectangleGeom(physicsSimulator, boxBody, boxWidth, boxHeight);
            boxTexture = new Texture2D(GraphicsDevice, (int)boxWidth, (int)boxHeight, 1, TextureUsage.None, SurfaceFormat.Color);
            Color[] color = new Color[(int)boxWidth * (int)boxHeight]; for (int i = 0; i < color.Length; i++) color[i] = Color.Red;
            boxTexture.SetData(color);

            platformBody = BodyFactory.Instance.CreateRectangleBody(physicsSimulator, platformWidth, platformHeight, 1);
            platformGeom = GeomFactory.Instance.CreateRectangleGeom(physicsSimulator, platformBody, platformWidth, platformHeight);
            platformBody.Position = new Vector2(1280/2,720/2);
            platformBody.IsStatic = true;
            platformTexture = new Texture2D(GraphicsDevice, (int)platformWidth, (int)platformHeight, 1, TextureUsage.None, SurfaceFormat.Color);
            Color[] bcolor = new Color[(int)platformWidth * (int)platformHeight]; for (int i = 0; i < bcolor.Length; i++) bcolor[i] = Color.Green;
            platformTexture.SetData(bcolor);
        }

        protected override void UnloadContent()
        {

        }

        protected override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
            KeyboardState keyboardState=Keyboard.GetState();
            if (keyboardState.IsKeyDown(Keys.Up)) boxBody.ApplyForce(new Vector2(0, -100));
            if (keyboardState.IsKeyDown(Keys.Down)) boxBody.ApplyForce(new Vector2(0, 100));
            if (keyboardState.IsKeyDown(Keys.Left)) boxBody.ApplyForce(new Vector2(-100, 0));
            if (keyboardState.IsKeyDown(Keys.Right)) boxBody.ApplyForce(new Vector2(100, 0));
            if (keyboardState.IsKeyDown(Keys.Space)) boxBody.ApplyImpulse(boxBody.LinearVelocity * -boxBody.Mass);
            physicsSimulator.Update(gameTime.ElapsedGameTime.Milliseconds * 0.001f);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            spriteBatch.Begin();
            spriteBatch.Draw(boxTexture, boxBody.Position, null, Color.White, boxBody.Rotation, new Vector2(boxTexture.Width / 2, boxTexture.Height / 2), 1, SpriteEffects.None, 0);
            spriteBatch.Draw(platformTexture, platformBody.Position, null, Color.White, platformBody.Rotation, new Vector2(platformTexture.Width / 2, platformTexture.Height / 2), 1, SpriteEffects.None, 0);
            spriteBatch.End();
            base.Draw(gameTime);
        }
    }
}

Файл проект FarseerEx1.zip

Комментарии