36.实现模型跟随地形的效果

这个教程是对引擎中的示例Sample03的一点解释,这个示例实现了一个坦克模型跟随地形移动的效果。因为我要控制坦克的炮塔、炮管、轮子的ModelMesh,所以需要从ModelSceneNode类继承实现一个tankModel类,因为事先不可能知道所使用的模型的结构,所以这个类没有放在引擎中,在实际制作游戏时需要从引擎的ModelSceneNode基类派生出你所使用的模型的类,代码如下:

namespace Sample03
{
    /// 
    /// 坦克模型,从ModelSceneNode继承,添加了层次模型的支持
    /// 
    public class tankModel : ModelSceneNode
    {
        #region 成员变量、属性和构造函数
                
        //需要跟随的地形
        SimpleTerrainSceneNode terrain;

        //坦克各部件的Bone。
        ModelBone turretBone;
        ModelBone cannonBone;
        ModelBone lSteerEngineBone;
        ModelBone rSteerEngineBone;
        ModelBone leftBackWheelBone;
        ModelBone rightBackWheelBone;
        ModelBone leftFrontWheelBone;
        ModelBone rightFrontWheelBone;
       
        //坦克各部件Bone的转换矩阵
        Matrix turretTransform;
        Matrix cannonTransform;
        Matrix lSteerEngineTransform;
        Matrix rSteerEngineTransform;
        Matrix leftBackWheelTransform;
        Matrix rightBackWheelTransform;
        Matrix leftFrontWheelTransform;
        Matrix rightFrontWheelTransform;
        
        //车轮自转的旋转矩阵
        Matrix wheelRollMatrix;

        //各部件的旋转量
        float turretRotationValue;
        float cannonRotationValue;
        float steerRotationValue;
        float wheelRotationValue;

        /// 
        /// 炮管、炮塔旋转的最大角速度
        /// 
        public const float MaxCannonSpeed = 1f;

        /// 
        /// 炮塔的旋转范围
        /// 
        private const float MaxTurretAngle = 1.0f;
        private const float MinTurretAngle = -1.0f;

        /// 
        /// 炮管的旋转范围
        /// 
        private const float MaxCannonAngle = 0.0f;
        private const float MinCannonAngle = -0.9f;

        /// 
        /// 获取或设置车轮的旋转量
        /// 
        public float WheelRotationValue
        {
            get { return wheelRotationValue; }
            set { wheelRotationValue = value; }
        }

        /// 
        /// 获取或设置前轮轴的旋转量
        /// 
        public float SteerRotationValue
        {
            get { return steerRotationValue; }
            set { steerRotationValue = value; }
        }

        /// 
        /// 获取或设置炮塔旋转量
        /// 
        public float TurretRotation
        {
            get { return turretRotationValue; }
            set
            {
                if (value > MaxTurretAngle)
                    turretRotationValue = MaxTurretAngle;
                else if (value < MinTurretAngle)
                    turretRotationValue = MinTurretAngle;
                else
                    turretRotationValue = value;
            }
        }

        /// 
        /// 获取或设置炮管旋转量
        /// 
        public float CannonRotation
        {
            get { return cannonRotationValue; }
            set
            {
                if (value > MaxCannonAngle)
                    cannonRotationValue = MaxCannonAngle;
                else if (value < MinCannonAngle)
                    cannonRotationValue = MinCannonAngle;
                else
                    cannonRotationValue = value;
            }
        }
                
        /// 
        /// 创建一个ModelSceneNode对象。
        /// 
        /// 引擎
        /// 模型名称
        public tankModel(StunXnaGE engine, Scene setScene, string modelAssetName,SimpleTerrainSceneNode terrain)
            : base(engine, setScene, modelAssetName)
        {
            this.terrain = terrain;
        }

        #endregion               

        /// 
        /// 初始化节点。
        /// 
        public override void Initialize()
        {
            base.Initialize();
            
            // 获取坦克各部件的Bone。
            turretBone = model.Bones["turret_geo"];
            cannonBone = model.Bones["canon_geo"];
            lSteerEngineBone = model.Bones["l_steer_geo"];
            rSteerEngineBone = model.Bones["r_steer_geo"];            
            leftBackWheelBone = model.Bones["l_back_wheel_geo"];
            rightBackWheelBone = model.Bones["r_back_wheel_geo"];
            leftFrontWheelBone = model.Bones["l_front_wheel_geo"];
            rightFrontWheelBone = model.Bones["r_front_wheel_geo"];
            
            //获取坦克各部件的转换矩阵
            turretTransform = turretBone.Transform;
            cannonTransform = cannonBone.Transform;
            lSteerEngineTransform =lSteerEngineBone .Transform ;
            rSteerEngineTransform = rSteerEngineBone.Transform;            
            leftBackWheelTransform = leftBackWheelBone.Transform;
            rightBackWheelTransform = rightBackWheelBone.Transform;
            leftFrontWheelTransform = leftFrontWheelBone.Transform;
            rightFrontWheelTransform = rightFrontWheelBone.Transform;
        }

        public override void Update(GameTime gameTime)
        {
            
            base.Update(gameTime);

            //根据旋转量重新计算变换矩阵
            turretBone.Transform = Matrix.CreateRotationY(turretRotationValue) * turretTransform;
            cannonBone.Transform = Matrix.CreateRotationX(cannonRotationValue) * cannonTransform;
            lSteerEngineBone.Transform=Matrix.CreateRotationY(steerRotationValue) * lSteerEngineTransform ;
            rSteerEngineBone.Transform = Matrix.CreateRotationY(steerRotationValue) * rSteerEngineTransform;
            
            wheelRollMatrix *= Matrix.CreateRotationX(wheelRotationValue );
            leftBackWheelBone.Transform = Matrix.CreateRotationX(wheelRotationValue ) * leftBackWheelTransform;
            rightBackWheelBone.Transform = Matrix.CreateRotationX(wheelRotationValue) * rightBackWheelTransform;
            leftFrontWheelBone.Transform = Matrix.CreateRotationX(wheelRotationValue) * leftFrontWheelTransform;
            rightFrontWheelBone.Transform = Matrix.CreateRotationX(wheelRotationValue) * rightFrontWheelTransform;            
        }
        
        /// 
        /// 绘制坦克。
        /// 
        ///         
        public override int Draw(GameTime gameTime,bool useReflect)
        {
            model.CopyAbsoluteBoneTransformsTo(modelTransforms);
            
            return base.Draw(gameTime,useReflect );  
        } 
    }
}

这个类主要添加了要控制的ModelMesh的信息,因为要实现地形跟随,还需要添加地形的引用。具体解释首先可参见简单动画-坦克1,然后是较难的4.8 可视化模型骨骼结构4.9 使Bone独立运动:模型动画

接着还要从相机类CameraSceneNode.cs中派生出跟随相机类ChaseCameraSceneNode.cs,代码如下:

namespace StunEngine.SceneNodes
{
    /// 
    /// 照相机类型
    /// 
    public enum CameraType
    {
        ThirdPersonFar,
        ThirdPersonClose,
        HighView
    }

    public class ChaseCameraSceneNode: CameraSceneNode
    {
        /// 
        /// 要跟踪的对象
        /// 
        SceneNode node;

        /// 
        /// 跟踪相机类型枚举
        /// 
        public CameraType currentCameraType = CameraType.ThirdPersonFar;
        
        /// 
        /// 相机位置偏离模型的距离。
        /// 
        Vector3 CameraPositionOffset;

        /// 
        /// 相机观察目标偏离的距离。
        /// 
        Vector3 CameraTargetOffset;

        /// 
        /// 创建一个跟随相机
        /// 
        /// 引擎
        /// 所属场景
        /// 要跟踪的对象
        public ChaseCameraSceneNode(StunXnaGE engine, Scene setScene, SceneNode node)
            : base(engine, setScene, Vector3.Zero, Vector3.Forward)
        {
            this.node = node;
        }

        /// 
        /// 更新视矩阵
        /// 
        /// 是否更新投影矩阵
        public override void  UpdateViewMatrix(bool updateProjection)
        {
            if (Input.KeyboardKeyJustPressed(Keys.Tab))
            {
                currentCameraType += 1;
                if (currentCameraType > CameraType.HighView)
                    currentCameraType = CameraType.ThirdPersonFar;
            }
            
            Vector3 cameraRotatedPosition;
            Vector3 cameraFinalPosition;
            Vector3 cameraFinalTargetPosition;

            switch (currentCameraType)
            {
                case CameraType.ThirdPersonFar:                    
                    CameraPositionOffset=new Vector3 (0, 15, -25);
                    CameraTargetOffset = new Vector3(0, 10, 0);
                    break;
                case CameraType .ThirdPersonClose :
                    CameraPositionOffset = new Vector3(0, 6, -9);
                    CameraTargetOffset = new Vector3(0, 6, 0);
                    break;
                case CameraType.HighView:
                    CameraPositionOffset = new Vector3(0, 40, -1);
                    CameraTargetOffset = new Vector3(0, 0, 0);
                    break;
                default:
                    throw new InvalidOperationException("照相机类型不存在");
            }

            cameraFinalTargetPosition = node.pose.Position + CameraTargetOffset;
            cameraRotatedPosition = Vector3.Transform(CameraPositionOffset, Quaternion.CreateFromAxisAngle(Vector3.Up, MathHelper.ToRadians(-node.AngleRotation.Y)));
            cameraFinalPosition = node.pose.Position + cameraRotatedPosition;

            viewMatrix = Matrix.CreateLookAt(cameraFinalPosition, cameraFinalTargetPosition, Vector3 .Up);

            //updateProjection为true则重新设置真实投影矩阵和逻辑投影矩阵
            if (updateProjection)
            {
                //  真实视锥体,基于很大的真实远裁平面           
                projectionMatrix = Matrix.CreatePerspectiveFieldOfView(cameraFov, AspectRatio, nearPlane, internalFarPlane);
                internalFarPlaneBoundingFrustum.Matrix = viewMatrix * projectionMatrix;

                //  基于逻辑远裁平面的逻辑视锥体。           
                farPM = Matrix.CreatePerspectiveFieldOfView(cameraFov, AspectRatio, nearPlane, FarPlane);
                farPlaneBoundingFrustum.Matrix = viewMatrix * farPM;
            }
            hasChanged = true;
        }
    }
}

这个类的作用是根据引用的模型的位置改变相机的属性,具体实现原理可参见XNA的帮助文件中的How To: Make a Third-Person Camera(http://msdn.microsoft.com/en-us/library/bb203909.aspx)。地形跟随是指让模型根据其下的地形调整高度,更难的是调整模型的倾斜程度,详细解释请参见4.17 根据地形正确倾斜模型,代码放在了从ModelController继承的tankModelController类中,代码如下:

namespace Sample03
{
    /// 
    /// 坦克模型控制器,从ModelController类继承,实现了坦克跟随地形的效果
    /// 
    public class tankModelController : ModelController
    {
        #region 构造函数和成员变量 
        
        private tankModel tankModel;  
        SimpleTerrainSceneNode terrain;

        private const int WALK_HEIGHT = 5;        

        /// 
        /// 创建一个新ModelController对象。
        /// 
        /// 
        public tankModelController(StunXnaGE engine,SimpleTerrainSceneNode terrain): base(engine)
        {
            this.terrain=terrain ;
        }
        #endregion

        
        /// 
        /// 主控制逻辑
        /// 
        /// 
        public override void ControllerAction(GameTime gameTime)
        {
            if (tankModel == null)
            {
                tankModel = (tankModel)controlledNode;
            }
            //每帧流逝的时间,秒为单位
            float timeDifference = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;
            ProcessInput(timeDifference);
        }
        
        
        private void ProcessInput(float elapsedTime)
        {
            float movement = 0;
            float rotation = 0;

            //坦克相机每帧旋转和移动距离
            float rotationPerFrame = elapsedTime * tankModel.RotationSpeed;
            float movementPerFrame = elapsedTime * tankModel.MovementSpeed;

            if (Input.KeyboardUpPressed || Input.Keyboard.IsKeyDown(Keys.W))
            {
                movement += movementPerFrame;
                
                //轮子的转动角度
                float theta = movementPerFrame / 1.8f;
                tankModel.WheelRotationValue += theta;
            }
            if (Input.KeyboardDownPressed || Input.Keyboard.IsKeyDown(Keys.S))
            {
                movement -= movementPerFrame;

                //轮子的转动角度
                float theta = movementPerFrame / 1.8f;
                tankModel.WheelRotationValue -= theta;
            }
            if (Input.KeyboardRightPressed || Input.Keyboard.IsKeyDown(Keys.D))
            {
                rotation += rotationPerFrame;
                
                //前轮轴的转动角度
                tankModel.SteerRotationValue = -10f;
                if (Input.KeyboardDownPressed)
                    tankModel.SteerRotationValue = 10f;
            }
            if (Input.KeyboardLeftPressed || Input.Keyboard.IsKeyDown(Keys.A))
            {
                rotation -= rotationPerFrame;

                //前轮轴的转动角度
                tankModel.SteerRotationValue = 10f;
                if (Input.KeyboardDownPressed)
                    tankModel.SteerRotationValue = -10f;
            }
            //如果没有左右转弯,则将前轮轴的转动角度设为0
            if (!(Input.KeyboardLeftPressed || Input.KeyboardRightPressed))
                tankModel.SteerRotationValue = 0;

            if (movement != 0 || rotation != 0)
            {
                Vector3 frontLeftOrig = new Vector3(0, 0, 0);
                Vector3 frontRightOrig = new Vector3(-90.54291f, -85.02f, 11.64229f);
                Vector3 backLeftOrig = new Vector3(-123.0109f, -121.891f, -16.23285f);
                Vector3 backRightOrig = new Vector3(123.0109f, -121.891f, -16.23285f);

                Matrix frontLeftMatrix = tankModel.Model.Bones["l_front_wheel_geo"].Transform;
                Matrix frontRightMatrix = tankModel.Model.Bones["r_front_wheel_geo"].Transform;
                Matrix backLeftMatrix = tankModel.Model.Bones["l_back_wheel_geo"].Transform;
                Matrix backRightMatrix = tankModel.Model.Bones["r_back_wheel_geo"].Transform;

                Vector3 frontLeft = Vector3.Transform(frontLeftOrig, frontLeftMatrix * tankModel.Pose.WorldMatrix);
                Vector3 frontRight = Vector3.Transform(frontRightOrig, frontLeftMatrix * tankModel.Pose.WorldMatrix);
                Vector3 backLeft = Vector3.Transform(backLeftOrig, frontLeftMatrix * tankModel.Pose.WorldMatrix);
                Vector3 backRight = Vector3.Transform(backRightOrig, frontLeftMatrix * tankModel.Pose.WorldMatrix);

                Vector3 front = (frontLeft + frontRight) / 2.0f;
                Vector3 back = (backLeft + backRight) / 2.0f;
                Vector3 backToFront = front - back;

                float frontTerHeight = terrain.GetExactHeightAt(front.X, -front.Z);
                float backTerHeight = terrain.GetExactHeightAt(back.X, -back.Z);
                float fbTerHeightDiff = frontTerHeight - backTerHeight;

                float fbAngle = (float)Math.Atan2(fbTerHeightDiff, backToFront.Length());

                Vector3 left = (frontLeft + backLeft) / 2.0f;
                Vector3 right = (frontRight + backRight) / 2.0f;
                Vector3 rightToLeft = left - right;

                float leftTerHeight = terrain.GetExactHeightAt(left.X, -left.Z);
                float rightTerHeight = terrain.GetExactHeightAt(right.X, -right.Z);
                float lrTerHeightDiff = leftTerHeight - rightTerHeight;

                float lrAngle = (float)Math.Atan2(lrTerHeightDiff, rightToLeft.Length());

                //设置坦克车身的旋转
                angle += rotation;
                tankModel.AngleRotation = new Vector3(fbAngle * 30, angle, -lrAngle * 30);
                tankModel.Pose.Rotation = Quaternion.CreateFromYawPitchRoll(MathHelper.ToRadians(-tankModel.AngleRotation.Y), MathHelper.ToRadians(-tankModel.AngleRotation.X), MathHelper.ToRadians(tankModel.AngleRotation.Z));
                tankModel.Pose.SetRotation(ref tankModel.Pose.Rotation);

                Vector3 addVector = Vector3.Transform(Vector3.Forward, Quaternion.CreateFromAxisAngle(Vector3.Up, MathHelper.ToRadians(-tankModel.AngleRotation.Y))) * movement;
                tankModel.Pose.Position -=addVector;
                tankModel.Pose.SetPosition(ref tankModel.Pose.Position);                
            }
            
            Vector3 tempPosition;
            //  检测坦克的高度,如果高度发生变化,则重新设置坦克的高度
            float height = WALK_HEIGHT + terrain.GetExactHeightAt(tankModel.Pose.Position.X, -tankModel.Pose.Position.Z);
            float diff = height - tankModel.Pose.Position.Y;
            if (diff != 0)
            {
                tempPosition = new Vector3(tankModel.Pose.Position.X, height, tankModel.Pose.Position.Z);
                tankModel.Pose.SetPosition(ref tempPosition);
            }

            //旋转炮台
            tankModel.TurretRotation -= Input.MouseXMovement * tankModel.MaxCannonSpeed*elapsedTime ;
            //旋转炮管
            tankModel.CannonRotation += Input.MouseYMovement * tankModel.MaxCannonSpeed * elapsedTime;

            //移动相机的位置跟随模型
            tankModel.Scene.Camera.Pose.SetPosition(ref tankModel.Pose.Position);
            tankModel.Scene.Camera.UpdateViewMatrix(true);
        }
    }
}

和教程4.17 根据地形正确倾斜模型不同之处在于,为了简化起见,我并没有使用自定义内容处理器获取坦克最低点的顶点坐标,而是直接写在了代码中,这样的话此代码只能用于这个坦克模型,如果你想使用自己的模型代替,还是参考这个教程使用自定义模型处理器吧。 另外在XNA官网上有一个示例Collision Series 5: Heightmap Collision with Normals(http://creators.xna.com/en-US/sample/collision3dheightmapnormals)也非常有参考价值,4.17 根据地形正确倾斜模型使用的方法是获取坦克四个轮子最低点下地形的坐标计算倾斜程度,而官网的例子是获取地形的法线计算倾斜程度,好像没有4.17 根据地形正确倾斜模型中使用的方法精确。

XNA示例截图

好了,至此StunEngine0.3已经告一段落,走到这一步花了我大约4年的时间。回头想来,一些本来认为很难的东西如果静心研究后发现不过如此,难的东西还在后面呢。虽然有点成就感,但是平心而论,目前的东西还是属于初级内容,若时间允许会逐步完善0.4版本的。

文件下载(已下载 1357 次)

发布时间:2010/5/11 上午10:32:14  阅读次数:7291

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号