TorqueX/Basic3DTutorial

From TDN

This page is a Work In Progress.


Contents

Introduction


This will be a simple tutorial on how to setup a player object that can switch between a third-person camera and a free-look debug camera.

Getting Started

Note: Portions shamelessly stolen from the FPS demo, but greatly pared down and simplified to reduce clutter for newbies.

The key to all this is to create a Controller component that acts like a traffic cop for what object is currently hooked up to the player's input and what camera is generating the output. Any object that takes input and has a camera for output will implement the IControllable interface. We'll have two:


1. PlayerController - the regular component that deals with controlling our player, taking the player's input and responding to any non-physics actions. Things like switching the camera view, switching weapons, firing, etc.

2. TestCamera - a component that does a simple free-look camera, except with an input mapped to switch back to the player.


FreeLook Camera

First, create and define a FreeLook test camera, both in the XML and in the code. You need to override FreeCameraComponent because your camera must implement IControllable, as well as adding buttons to the input map to switch back to the player's camera. Be sure to grab the ControlComponent.cs file from the player folder in the FPSDemo.

XML Object (replace the existing one in the starter XML).

    <TorqueObject type="GarageGames.Torque.Core.TorqueObject" name="Camera">
      <Components>
        <CameraComponent type="StarterGame3D.TestCamera" name="CameraComponent">
          <FarDistance>3000</FarDistance>
          <FOV>1.57</FOV>
        </CameraComponent>
        <SceneComponent type="GarageGames.Torque.T3D.T3DSceneComponent">
          <Position>
            <X>1024</X>
            <Y>1024</Y>
            <Z>300</Z>
          </Position>
        </SceneComponent>
      </Components>
    </TorqueObject>


Code in TestCamera.cs:

using System;
using System.Collections.Generic;
using System.Text;
using GarageGames.Torque.T3D;
using GarageGames.Torque.Sim;
using GarageGames.Torque.Platform;
using Microsoft.Xna.Framework.Input;

namespace StarterGame3D
{
    /// <summary>
    /// A freelook test camera
    /// </summary>
    public class TestCamera : FreeCameraComponent, IControllable
    {
        //======================================================
        #region Public properties

        public ControllerComponent Controller
        {
            get { return _controller; }
            set { _controller = value; }
        }

        #endregion

        //======================================================
        #region Public methods

        /// <summary>
        /// Returns the input map used by the specified player.
        /// </summary>
        /// <param name="playerIndex">The player index of this camera.</param>
        /// <returns>The input map used by this camera.</returns>
        public InputMap GetInputMap(int playerIndex)
        {
            if (playerIndex != PlayerIndex)
                return null;

            return InputMap;
        }

        protected void ToggleControlObject(float val)
        {
            if (val > 0.0f)
                _controller.UseBaseControlObject();
        }

        public void OnControlGained(int playerIndex)
        {
            Game.Instance.SceneView.Camera = this;
        }

        public void OnControlLost() { }

        #endregion

        //======================================================
        #region Private, protected, internal methods

        protected override void _SetupInputMap()
        {
            base._SetupInputMap();

            int gamepadId = InputManager.Instance.FindDevice("gamepad" + PlayerIndex);
            if (gamepadId >= 0)
            {
                InputMap.BindAction(gamepadId, (int)XGamePadDevice.GamePadObjects.Y, ToggleControlObject);
            }

            int keyboardId = InputManager.Instance.FindDevice("keyboard");
            if (keyboardId >= 0)
                InputMap.BindAction(keyboardId, (int)Keys.Y, ToggleControlObject);
        }

        #endregion

        //======================================================
        #region Private, protected, internal fields

        ControllerComponent _controller;

        #endregion
    }
}

Player

On your player (note, some components omitted), make sure you have a Controller component, a PlayerComponent, and a CameraComponent. We'll also rely on the default physics implementation to move us forward/backward, etc (see below for that XML).


    <Player type="GarageGames.Torque.Core.TorqueObject" name="PlayerTemplate">
      <IsTemplate>true</IsTemplate>
      <ObjectType>
        <object objTypeRef="Player" />
      </ObjectType>
      <Components>
        <ControllerComponent type="StarterGame3D.ControllerComponent" />
        
        <PlayerComponent type="StarterGame3D.PlayerComponent">
          <SceneGroupName>PlayerMesh</SceneGroupName>
          <PlayerIndex>1</PlayerIndex>
        </PlayerComponent>
      

        <CameraComponent type="StarterGame3D.PlayerCameraComponent">
          <SceneGroupName>PlayerMesh</SceneGroupName>
          <TransformInterfaceName>cam</TransformInterfaceName>
          <CameraSpeed>50</CameraSpeed>
          <FarDistance>1000</FarDistance>
          <FOV>1.57</FOV>
          <BoundaryObjectTypes>
            <object objTypeRef="Terrain" />
            <object objTypeRef="StaticGeometry" />
          </BoundaryObjectTypes>
          <RigidManager nameRef="RigidManager" />
          <PositionOffset>
            <X>0.0</X>
            <Y>-9.0</Y>
            <Z>1</Z>
          </PositionOffset>
          <RotationOffset>
            <X>0</X>
            <Y>0</Y>
            <Z>0</Z>
          </RotationOffset>
        </CameraComponent>
      </Components>

    </Player>

Controller

Get the IControllable interface and ControllerComponent from the FPS starter demo, I used them essentially unchanged. They're in the player folder.


Player Camera

PlayerCameraComponent. Notice that I am exposing an Update() method that will make the camera update its position. The FPS demo camera seems to work as a side-effect of the fact that the view angle is constantly being updated, which causes T3DCameraComponent to set the TransformDirty flag and recalculate the transform matrix.

But in my attempts I wasn't doing that since I don't have a view angle, so my camera was just stationary in the world and didn't appear to be mounted to the vehicle. I just assumed that any T3DCameraComponent derived object would automatically track its associated owner, similar to the way the control/physics automatically deals with moving, but it doesn't.

PlayerCameraComponent.cs

using System;
using System.Collections.Generic;
using System.Text;
using GarageGames.Torque.Core;
using GarageGames.Torque.T3D;
using Microsoft.Xna.Framework;

namespace StarterGame3D
{
    /// <summary>
    /// Third person camera
    /// </summary>
    public class PlayerCameraComponent : T3DCameraComponent
    {
        //======================================================
        #region Public methods

        public override void CopyTo(TorqueComponent obj)
        {
            base.CopyTo(obj);
            PlayerCameraComponent obj2 = (PlayerCameraComponent)obj;
        }

        public void Update()
        {
            //we want to trigger a dirty flag so the matrix will get recalculated
            this.RotatedPositionOffset = RotatedPositionOffset + Vector3.Zero;
        }

        #endregion

        //======================================================
        #region Private, protected, internal methods

        protected override bool _OnRegister(TorqueObject owner)
        {
            if (!base._OnRegister(owner))
                return false;

            return true;
        }

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

        #endregion
    }
}

PlayerComponent

Now the PlayerComponent, which sets up the input map. The ControlComponent will automatically handle moving around on the ground (see below).

PlayerComponent.cs

using System;
using System.Collections.Generic;
using System.Text;
using GarageGames.Torque.T3D;
using GarageGames.Torque.Core;
using GarageGames.Torque.Sim;
using GarageGames.Torque.Platform;
using Microsoft.Xna.Framework.Input;
using GarageGames.Torque.GUI;

namespace StarterGame3D
{
   public class PlayerComponent : T3DInputComponent, IControllable
    {
        //======================================================
        #region Public properties, operators, constants, and enums

        public TestCamera FreeCamera
        {
            get { return _freeCamera; }
            set { _freeCamera = value; }
        }

        #endregion

        //======================================================
        #region Public methods

         public override void CopyTo(TorqueComponent obj)
        {
            base.CopyTo(obj);

            //TODO: make sure to copy properties here
            (obj as PlayerComponent).PlayerIndex = this.PlayerIndex;
        }

        #endregion

        //======================================================
        #region Private, protected, internal methods

        protected override bool _OnRegister(TorqueObject owner)
        {
            if (!base._OnRegister(owner))
                return false;

            return true;
        }

        protected override void _PostRegister()
        {
            base._PostRegister();

            _controllerComponent = Owner.Components.FindComponent<ControllerComponent>();
        }

        protected override void _RegisterInterfaces(TorqueObject owner)
        {
            base._RegisterInterfaces(owner);

            // todo: register interfaces to be accessed by other components
            // E.g.,
            // Owner.RegisterCachedInterface("float", "interface name", this, _ourInterface);
        }

        protected override void _SetupInput(InputMap inputMap, int gamepad, int keyboard)
        {
            // move
            inputMap.BindMove(gamepad, (int)XGamePadDevice.GamePadObjects.LeftThumbX
                                     , MoveMapTypes.StickAnalogHorizontal, 0);
            inputMap.BindMove(gamepad, (int)XGamePadDevice.GamePadObjects.LeftThumbY
                                     , MoveMapTypes.StickAnalogVertical, 0);

            // look
            inputMap.BindMove(gamepad, (int)XGamePadDevice.GamePadObjects.RightThumbX
                                     , MoveMapTypes.StickAnalogHorizontal, 1);
            inputMap.BindMove(gamepad, (int)XGamePadDevice.GamePadObjects.RightThumbY
                                     , MoveMapTypes.StickAnalogVertical, 1);

            inputMap.BindAction(gamepad, (int)XGamePadDevice.GamePadObjects.Y, ToggleCamera);

            // do keyboard
            {
                // wasd
                inputMap.BindMove(keyboard, (int)Keys.D, MoveMapTypes.StickDigitalRight, 0);
                inputMap.BindMove(keyboard, (int)Keys.A, MoveMapTypes.StickDigitalLeft, 0);
                inputMap.BindMove(keyboard, (int)Keys.W, MoveMapTypes.StickDigitalUp, 0);
                inputMap.BindMove(keyboard, (int)Keys.S, MoveMapTypes.StickDigitalDown, 0);

                // arrows
                inputMap.BindMove(keyboard, (int)Keys.Right, MoveMapTypes.StickDigitalRight, 1);
                inputMap.BindMove(keyboard, (int)Keys.Left, MoveMapTypes.StickDigitalLeft, 1);
                inputMap.BindMove(keyboard, (int)Keys.Up, MoveMapTypes.StickDigitalUp, 1);
                inputMap.BindMove(keyboard, (int)Keys.Down, MoveMapTypes.StickDigitalDown, 1);

                inputMap.BindAction(keyboard, (int)Keys.Y, ToggleCamera);
            }
        }

        protected void ToggleCamera(float val)
        {
            if (val < 1.0f)
                return;

            _controllerComponent.SetControlObject(_freeCamera as IControllable);
        }

        protected override void _UpdateInput(Move move, float dt)
        {
            PlayerCameraComponent cam = Owner.Components.FindComponent<PlayerCameraComponent>();
            if (cam != null)
            {
                cam.Update();
            }
        }

        #endregion

        //======================================================
        #region Private, protected, internal fields

        private ControllerComponent _controllerComponent;
        private TestCamera _freeCamera;

        #endregion

        #region IControllable Members

        public ControllerComponent Controller
        {
            get
            {
                return null;
            }
            set
            {
            }
        }

        public InputMap GetInputMap(int playerIndex)
        {
            return _inputMap;
        }

        public void OnControlGained(int playerIndex)
        {
            PlayerCameraComponent camera = Owner.Components.FindComponent<PlayerCameraComponent>();
            //use same world index or we'll get the aborted error on the clip map texture
            camera.WorldViewIndex = FreeCamera.WorldViewIndex;
            GUISceneview sceneView = Game.Instance.SceneView;
            sceneView.Camera = camera;
        }

        public void OnControlLost()
        {
            
        }

        #endregion
    }
}

Physics

Now the control/physics defined on the player in the XML. To change the physics of your player you would override the control component and states and define custom ones here in the XML that will map to those classes. Then those states can adjust things like movement speed, etc.

        <ControlComponent type="GarageGames.Torque.T3D.T3DGroundControlComponent">
          <SceneGroupName>PlayerMesh</SceneGroupName>
          <ControlStates>
            <ControlState>
              <Name>OnGround</Name>
              <Type>GarageGames.Torque.T3D.OnGroundControlState</Type>
            </ControlState>
            <ControlState>
              <Name>InAir</Name>
              <Type>GarageGames.Torque.T3D.InAirControlState</Type>
            </ControlState>
          </ControlStates>
          <StartState>InAir</StartState>
        </ControlComponent>

        <RigidComponent type="GarageGames.Torque.T3D.T3DRigidComponent">
          <SceneGroupName>PlayerMesh</SceneGroupName>
          <RenderCollisionBounds>false</RenderCollisionBounds>
          <ResolveCollisions>true</ResolveCollisions>
          <CollisionShapes>
            <CollisionShape>
              <Shape type="GarageGames.Torque.T3D.RigidCollision.CollisionSphereShape">
                <Radius>5.0</Radius>
                <Center>
                  <X>0.0</X>
                  <Y>0.0</Y>
                  <Z>0.0</Z>
                </Center>
              </Shape>
            </CollisionShape>
          </CollisionShapes>
          <RotationScale>0.0</RotationScale>
          <GravityScale>1.5</GravityScale>
          <RigidManager nameRef="RigidManager" />
          <RigidMaterial type="GarageGames.Torque.T3D.RigidCollision.RigidMaterial">
            <Restitution>0.0</Restitution>
          </RigidMaterial>
        </RigidComponent>


Game.BeginRun

Lastly, in BeginRun() we'll go ahead and kick off this game.

        private GUISceneview _sceneView;
        public GUISceneview SceneView
        {
            get { return _sceneView; }
        }


        protected override void BeginRun()
        {

           base.BeginRun();

            // load our scene objects from XML. 
            SceneLoader.Load(@"data\levels\levelData.txscene");

            //set sceneview
            _sceneView = TorqueObjectDatabase.Instance.FindObject<GUISceneview>("DefaultSceneView");

            //grab our free camera, we'll fake playerindex for now
            TestCamera freeCamera = TorqueObjectDatabase.Instance.FindObject<TestCamera>("CameraComponent");
            freeCamera.PlayerIndex = 1;


            //get player template and clone it
            TorqueObject template = TorqueObjectDatabase.Instance.FindObject<TorqueObject>("PlayerTemplate");
            TorqueObject player = template.CloneT();
            player.Name = "Player1";

            //setup components on instance before we register it
            ControllerComponent controller = player.Components.FindComponent<ControllerComponent>();
            PlayerComponent playerComponent = player.Components.FindComponent<PlayerComponent>();
            PlayerCameraComponent playerCamera = player.Components.FindComponent<PlayerCameraComponent>();
            
            controller.BaseControlObject = playerComponent;
            controller.PlayerIndex = 1;
            playerComponent.PlayerIndex = 1;

            //now register
            if (!TorqueObjectDatabase.Instance.Register(player))
            {
                System.Diagnostics.Debug.WriteLine("Failed to register!");
            }

            //then setup control
            controller.UseBaseControlObject();

            //make sure gui is prepared
            GUICanvas.Instance.OnPreRender();

            //assign freecam to player
            playerComponent.FreeCamera = freeCamera;

            //now assign camera to start off with
            _sceneView.Camera = playerCamera;
        }


Conclusion

I hope this has been useful in introducing you to TorqueX 3D.