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