2.场景中的对象-SceneNode基类

建立了场景类之后,接下来就要处理场景中的所有对象。一般说来,场景中应该包含2D图像、文字、相机、天空盒(球)、地形、模型等。因此应该将这些对象的共性抽象出来作为所有对象的基类,本教程叫做SceneNode类(有些游戏也称作GameObject或BaseEntity),所有对象都包含的属性应该是位置、旋转、缩放,所以在SceneNode命名空间下建立一个SceneNode类:

namespace StunEngine.SceneNodes
{
    /// <summary>
    /// 这是引擎场景图的基类,引擎中的所有对象都从这个类继承。   
    /// SceneNode拥有实体的基本属性:位置,缩放,旋转和Engine。
    /// </summary>
    public abstract class SceneNode
    {
        构造函数和成员变量

        属性

        /// <summary>
        /// 更新控制器
        /// </summary>
        /// <param name="gameTime"></param>
        public virtual void Update(GameTime gameTime)
        {
            if (controllers.Count > 0)
                for (int i = 0; i < controllers.Count; i++)
                    controllers[i].Update(gameTime);
        }

        /// <summary>
        /// 初始化
        /// </summary>
        public abstract void Initialize();

        /// <summary>
        /// 返回包含node位置的Vector3
        /// </summary>
        /// <param name="position"></param>        
        public void GetPosition(out Vector3 position)
        {
            position = pose.Position;
        }

        /// <summary>
        /// 在这个SceneNode上连接一个新Controller
        /// </summary>
        /// <param name="controller">IController接口</param>
        public void AttachController(IController controller)
        {
            if (!controllers.Contains(controller))
            {
                controllers.Add(controller);
                controller.AttachNode(this);
            }
        }

        /// <summary>
        /// 从这个SceneNode移除一个Controller
        /// </summary>
        /// <param name="controller">IController接口</param>
        public void DetachController(IController controller)
        {
            if (controllers.Contains(controller))
            {
                controllers.Remove(controller);
                controller.DetachNode();
            }
        }        
    }    
}

从代码中可见,这个类并没有直接包含对象的Position、Scale和Rotation,而是把这些属性都放在了一个称为WorldPose的新类中,这样做可以更加灵活地处理这些属性,而且以后还可以在WorldPose类中进行BB和AABB碰撞检测。这种做法与《Beginning XNA 3.0 Game Programming From Novice to Professional》(以后简称《BeginningXNA》)一书中第十章(在这本书的XNA2.0版本中是第九章)“Lights, Camera, Transformations!”的方法是一样的,只不过该书中对应的类是Transformation类。

WorldPose类的代码如下所示:

namespace StunEngine
{
    /// <summary>
    /// 表示一个对象在世界中的位置
    /// </summary>
    public class WorldPose
    {

        /// <summary>
        /// 构造函数
        /// </summary>
        public WorldPose()
        {
            Rotation = Quaternion.Identity;
            Position = Vector3.Zero;
            Scale = Vector3.One;
            UpdateMatrix();
        }

        /// <summary>
        /// 缩放
        /// 注意这个公有成员必须使用SetScale()方法进行改变
        /// </summary>
        public Vector3 Scale;
        /// <summary>
        /// 位置
        /// 注意这个公有成员必须使用SetPosition()方法进行改变
        /// </summary>
        public Vector3 Position;
        /// <summary>
        /// 旋转
        /// 注意这个公有成员必须使用SetRotation()方法进行改变
        /// </summary>
        public Quaternion Rotation;
        /// <summary>
        /// 世界变换矩阵
        /// 注意这个公有成员不能改变,它是基于SROT因子进行计算的
        /// </summary>
        public Matrix WorldMatrix;
        /// <summary>
        /// 对应的节点
        /// </summary>
        public SceneNode Node;

        internal Matrix ScaleMatrix;
        internal Matrix RotationMatrix;
        internal Matrix TranslateMatrix;

        /// <summary>
        /// 设置一个新位置
        /// </summary>
        /// <param name="position"></param>
        public void SetPosition(ref Vector3 position)
        {
            Position = position;
            TranslateMatrix = Matrix.CreateTranslation(Position);
            RebuildWorldMatrix(); 
        }

        /// <summary>
        /// 设置一个新旋转
        /// </summary>
        /// <param name="rot"></param>
        public void SetRotation(ref Quaternion rot)
        {
            Rotation = rot;
            RotationMatrix = Matrix.CreateFromQuaternion(Rotation);
            RebuildWorldMatrix();
        }

        /// <summary>
        /// 设置一个新缩放
        /// </summary>
        /// <param name="scale"></param>
        public void SetScale(ref Vector3 scale)
        {
            Scale = scale;            
            ScaleMatrix = Matrix.CreateScale(Scale);
            RebuildWorldMatrix();            
        }        

        /// <summary>
        /// 计算世界矩阵(按SROT的顺序)
        /// </summary>
        public void UpdateMatrix()
        {
            ScaleMatrix = Matrix.CreateScale(Scale);
            RotationMatrix = Matrix.CreateFromQuaternion(Rotation);
            TranslateMatrix = Matrix.CreateTranslation(Position);
            RebuildWorldMatrix();
        }

        private void RebuildWorldMatrix()
        {
            Matrix.Multiply(ref ScaleMatrix, ref RotationMatrix, out WorldMatrix);
            WorldMatrix.Translation = Position;
        }
    }
}

在SceneNode类中还可以看到一个类型为IController的集合controllers以及管理这个集合的AttachController和DetachController方法,并且在Update方法中调用了这个集合中所有元素的Update方法。这主要是处理用户输入的,直接在SceneNode类中实现用户输入肯定是不可取的,因为实际游戏的情况非常复杂,有时想控制一个相机的移动,有时想控制场景中一个模型的移动,一种做法是创建一个Player类,在Player类中包含要控制的SceneNode,通过改变ScenNode的位置、旋转或缩放实现用户控制。

本引擎采用的做法是在SceneNode中包含一个Controller类集合,可以随时通过添加或移除其中的Controller实现对不同对象的控制,代码位于Controllers命名空间,包括IController.cs接口、Controller.cs基类,其他控制器类都是从这个基类继承的,还有实际进行用户输入管理的Input.cs。

其中Input.cs直接来自于《ProfessionalXNA》的10.1Input类,比较长这里就不写了,可去看看教程或代码中的注释,我很喜欢这个静态类,可以在引擎的任何地方方便地进行调用,如果说要进行什么改进的话,应该就是实现一个手柄映射,只需编写手柄控制代码,它会自动映射到键盘,这样移植到Xbox360平台更加方便,映射的方法可参见《BeginningXNA》中的12.4.2 管理游戏设置

下面是IController.cs接口的代码,规定了Controller.cs类中必须要实现的方法:

namespace StunEngine.Controllers
{
    /// <summary>
    /// 定义ISceneNode控制器
    /// </summary>
    public interface IController
    {
        /// <summary>
        /// 获取或设置enabled状态
        /// </summary>
        bool    Enabled { get; set; }

        /// <summary>
        /// 更新控制器,由ControllerAction()调用
        /// </summary>
        /// <param name="gameTime"></param>
        void    Update(GameTime gameTime);
        
        /// <summary>
        /// 链接一个控制器
        /// </summary>
        /// <param name="node">要链接的节点</param>
        void    AttachNode(SceneNode node);
        
        /// <summary>
        /// 移除控制器ISceneNode.
        /// </summary>
        void    DetachNode();

        /// <summary>
        /// 控制器逻辑
        /// </summary>
        /// <param name="gameTime"></param>
        void    ControllerAction(GameTime gameTime);
    }
}

之后Controller.cs类实现这个接口:

namespace StunEngine.Controllers
{
    /// <summary>
    /// SceneNode Controlle抽象类,是所有controllers的基类
    /// </summary>
    public abstract class Controller : IController  
    {
        /// <summary>
        /// 创建一个新Controller对象
        /// </summary>        
        public Controller(StunXnaGE engine)
        {
            this.engine = engine;
        }
        
        /// <summary>
        /// 指向引擎的引用
        /// </summary>
        protected StunXnaGE        engine;

        /// <summary>
        /// 此对象控制的节点
        /// </summary>
        protected SceneNode    controlledNode;        

        /// <summary>
        /// controllers主要控制逻辑 
        /// 在Controller.Update()中调用,间接由SceneNode.Update()调用
        /// 必须被重写!
        /// </summary>
        /// <param name="gameTime"></param>
        public abstract void ControllerAction(GameTime gameTime);

        /// <summary>
        /// 设置控制的节点
        /// </summary>
        /// <param name="node"></param>
        public virtual void AttachNode(SceneNode node)
        {
            if (controlledNode != null)
            {
                SceneNode tmp = controlledNode;
                controlledNode = null;
                tmp.DetachController(this);
            }

            node.AttachController(this);
            controlledNode = node;
        }
        /// <summary>
        /// 移除控制的节点
        /// </summary>
        public virtual void DetachNode()
        {
            if (controlledNode != null)
            {
                SceneNode tmp = controlledNode;
                controlledNode = null;
                tmp.DetachController(this);
            }
        }        
        
        private bool isEnabled = true;        

        /// <summary>
        /// 打开/关闭控制器
        /// </summary>
        public bool Enabled
        {
            get { return isEnabled; }
            set {isEnabled = value;}            
        }
       
        /// <summary>
        /// 如果isEnabled则执行ControllerAction方法,这个方法被SceneNode.Update()调用
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime)
        {
            if (isEnabled && controlledNode!= null && engine.IsActive)
                ControllerAction(gameTime);
        }        
    }
    
}

以上就是场景中所有对象基类的代码,下一个教程会编写从这个抽象类继承的不同对象类。


发布时间:2009/11/20 下午2:11:51  阅读次数:7582

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

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号