Game Development Community

Managing in-game lives in TorqueX

by Ron Barbosa · in Torque X 2D · 04/02/2010 (5:59 pm) · 17 replies

Hey all...I am in the process of prototyping my next game for Xbox LIVE Indie Games.

I've come to a kind of stumbling block and I was hoping to get some feedback.

I'm curious to know what the best method is to managing a player object that has multiple lives. I've created a destructible component, so my player object is now capable of losing health. The destructible component marks the player scene object for deletion when health is depleted (this may not be a good idea).

I've tried to "re-register" the same scene object and unmark it for deletion to no avail.

So I'm curious...should my player object be a template that I clone? I usually think of templates as "drones" rather than key components that have persistent and progressive attributes.

Some enemy objects have a reference to my player scene object (so they can pursue and shoot him), but if I make him a template, then I have to notify all of those enemies when he's deleted and respawned.

So I'm not sure what the best mechanism is to have a SceneObject...allow it to be destroyed (and possibly marked for deletion) and have him respawn and be recognized by other objects in the game.

Any help would be greatly appreciated.

Thanks
--RB

#1
04/02/2010 (11:37 pm)
Have you looked into using the player manager to store values between levels?
#2
04/03/2010 (7:24 am)
I have not, but I will now. =P

I'm not too familiar with the player manager's intended function. I've only really interacted with it for input mapping.

I will look further into it to see if it will help with my current problem.

One thing I'm concerned with is persistence. In TXB BadGuy1 (or more accurately the ScenedObject with script name "BadGuy1") chases MainPlayer (again, the SceneObject with script name "MainPlayer")...but once MainPlayer dies (and BadGuy1 is still alive) I will need my respawned player object to be immediately recognized as "MainPlayer."

And that's the part I'm not quite sure how to accomplish...it might be simpler than I'm making it.

Thanks for the response, Raymond...I'll look into the PlayerManager.

--RB
#3
04/03/2010 (11:00 pm)
Hi, I'm using Schilling's gamekit, and have attached his takesdamage component to my PC. After reading your post it occured to me that I will need this in my game too.

if (_hitPoints <= 0)
                {
                    Vector2 respawn = new Vector2(0,0);

                    if (_sceneObject.Name.Equals("player"))
                    {
                        _sceneObject.Position = respawn;
                        _hitPoints = 4;
                        return;
                    }
                }

For some reason that I don't understand Torque won't let me say

_sceneObject.Position.X = 0;

It tells me I can't modify the return value of .Position because it is not a variable?? But setting it to a Vector is fine??
#4
04/04/2010 (5:45 am)
Position.X is not 'settable' - you can read the value but you can't set it. To update Position.X you have to assign a new vector to Position because the X and Y in a Vector2D are, as it were, readonly.
#5
04/04/2010 (6:50 am)
@Darthuvius...Duncan's right there. Once a Vector2 is established, you can't individually set its X and Y values.

This is actually an XNA Framework thing and not a Torque X thing.

So let me see if I get what you're doing here. You're artificially extending the life of the dying scene object if it is the player object. That's fair I guess. Then presumably you're going through some respawn logic and bringing him back to life?

While it doesn't answer my original question, I guess it does solve my original problem. ;)

Good stuff, Darthuvius...thanks!
--RB
#6
04/04/2010 (12:12 pm)
I will do this in a few posts but here is my player component.

using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;

using GarageGames.Torque.Core;
using GarageGames.Torque.Util;
using GarageGames.Torque.Sim;
using GarageGames.Torque.T2D;
using GarageGames.Torque.GUI;
using GarageGames.Torque.SceneGraph;
using GarageGames.Torque.MathUtil;

namespace StarterGame2D
{
    [TorqueXmlSchemaType]
    public class gp_PlayerComponent : TorqueComponent, ITickObject
    {
        //======================================================
        #region Static methods, fields, constructors
        #endregion

        //======================================================
        #region Constructors
        #endregion

        //======================================================
        #region Public properties, operators, constants, and enums

        public T2DSceneObject SceneObject
        {
            get { return Owner as T2DSceneObject; }
        }

        public static gp_PlayerComponent Instance
        {
            get { return _instance; }
        }

        public T2DSceneObject Playa
        {
            get { return _player; }
            set { _player = value; }
        }

        public T2DSceneObject SpawnSafe
        {
            get { return _spawnSafe; }
            set { _spawnSafe = value; }
        }

        public int StartHitPoints
        {
            get { return _startHP; }
            set { _startHP = value; }
        }

        public Vector2 CheckPoint
        {
            get { return _checkPoint; }
            set { _checkPoint = value; }
        }

        public bool KillEmAll
        {
            get { return _killEm; }
            set { _killEm = value; }
        }



        #endregion
#7
04/04/2010 (12:14 pm)
Public Region
#region Public methods

        public virtual void ProcessTick(Move move, float dt)
        {
            if (Game.Instance.Lives >= 0)
            {
                Game.Instance.HealthLevel = Math.Max(_damage.HitPoints, 0);
                if (_damage.HitPoints <= 0 && !_restartPending)
                {
                    _player.Visible = false;
                    Game.Instance.Lives -= 1;

                    _player.Position = Vector2.Zero;

                    DeadPlayer(_player);
                }
            }
        }

        public virtual void InterpolateTick(float k)
        {
            // todo: interpolate between ticks as needed here
        }

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

        public void ScheduledRestart(object sender, ScheduledEventArguments scheduleEventArguments)
        {
            _restartPending = false;
            spawnPlayer();
        }

        public void spawnPlayer()
        {
            if (_killEm)
            {
                ulong bitmask = 0;

                List<T2DSceneObject> _items = TorqueObjectDatabase.Instance.FindRegisteredObjects<T2DSceneObject>();
                TorqueObjectType bonus = TorqueObjectDatabase.Instance.GetObjectType("nme");
                bitmask = bonus.Bits;
                bonus = TorqueObjectDatabase.Instance.GetObjectType("BonusPickup");
                bitmask |= bonus.Bits;
                bonus = TorqueObjectDatabase.Instance.GetObjectType("nme_bullet");
                bitmask |= bonus.Bits;


                for (int i = 0; i < _items.Count; i++)
                {
                    if ((_items[i].ObjectType.Bits & bitmask) != 0)
                        _items[i].MarkForDelete = true;
                }
            }

            _player.Position = _checkPoint;
            _damage = _player.Components.FindComponent<gp_TakesDamage>();
            _damage.HitPoints = _startHP;
            Game.Instance.HealthLevel = _damage.HitPoints;
            _player.Visible = true;
            if (_spawnSafe != null)
            {
                T2DSceneObject safety = _spawnSafe.Clone() as T2DSceneObject;
                TorqueObjectDatabase.Instance.Register(safety);
                safety.Mount(_player, "", false);
            }

            T2DSceneCamera _mainCamera = TorqueObjectDatabase.Instance.FindObject<T2DSceneCamera>("Camera");
            _mainCamera.SetPosition(_player.Position, true);
        }

        public void DeadPlayer(T2DSceneObject dead)
        {
            if (Game.Instance.Lives >= 0)
            {
                if (!_instance._restartPending)
                {
                    iTmp = Game.Instance.Engine.GameTimeSchedule.Schedule(4000, _instance.ScheduledRestart);
                    _instance._restartPending = true;
                }
            }
            else
            {
                // we're really dead now
                GuiScoreScreen myScore = new GuiScoreScreen();
                GUICanvas.Instance.PushDialogControl(myScore, 0);
            }
        }

        #endregion
#8
04/04/2010 (12:15 pm)
#region Private, protected, internal methods

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

            if (_instance != null)
            {
                return false;
            }
            _instance = this;

            spawnPlayer();

            ProcessList.Instance.AddTickCallback(Owner, this);
            return true;
        }

        protected override void _OnUnregister()
        {
            base._OnUnregister();
            _instance = null;
            if (_restartPending)
                Game.Instance.Engine.GameTimeSchedule.Remove(iTmp);
        }

        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);
        }

        #endregion

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

        static gp_PlayerComponent _instance;
        bool _restartPending = false;
        T2DSceneObject _player;
        T2DSceneObject _spawnSafe;
        gp_TakesDamage _damage;
        int _startHP;
        Vector2 _checkPoint;
        bool _killEm;
        float _collectTime;
        int iTmp;

        #endregion
    }
}
#9
04/04/2010 (12:22 pm)
OK I don't know if this is the best worst whatever but it works for me. I make the hero invisible when dead, there is code in my chaseing shooting components that tell it to ignore an invisible player.

if (_target.Visible)
   do the things

I also wipe out any spawned enemeies and pickups and bullets when respawning my hero. The checkpoint is a vector for where it will spawn on screen, since I have the camera attached I need to set the position of that as well.

_spawnSafe is a particle effect that collides with enemy bullets so that we get a safe spawn without getting shot. It also makes for a cool effect. The effect has kill set on it so that it goes away after 2 3 secs.

I adapted this from the space warrior example.




#10
04/04/2010 (12:38 pm)
@Henry...thanks for the code samples and guidance.

This is kind of the same thing as what I was doing, only instead of the "Visible" flag, I was using the "MarkForDelete" flag. The problem with this is that once the object was marked for delete, reviving it is not handled very well.

I will have to alter my Destructible component to handle my player differently and not mark him for deletion. Then I will have to go back to all the areas where I was using the MarkForDelete flag, and create a different flag/condition to make my NPCs respond differently to the dead character. Visible is probably a good flag to use because it's part of the base SceneObject so I won't need to go through the process of finding my "PlayerComponent" and checking if it's alive or dead. Although I guess, I can add a "Dead" flag to the base T2DSceneObject (source code has its privileges). ;)

Thanks for all the responses!
--RB
#11
04/04/2010 (4:54 pm)
Right in the destructible component I added a bool for the player, basically if its the player don't delete it.
#12
04/05/2010 (11:20 am)
You might try detaching the game player state from the visible scene object. Perhaps have a TorqueObject that manages the game on the TorqueObjectDatabase so that the ProcessTick is called each frame and can be accessed easily by other objects that might have use of that data. It could check to see if the player's object has been destroyed, and if so, spawn another and take any action related to that.

Perhaps look at the Model-View-Controller design pattern.
#13
04/05/2010 (3:40 pm)
Good advice...the MVC would work.

Personally, I keep game state separate from things like score and number of lives.

I have a game state manager class that keeps track of actual game state (Initializing, ShowingSplashScreen, InMainMenu, Paused, GameOver, etc.). This class is an observable class, and interested modules can subscribe to be notified of game state changes. When state changes, the subscribers are notified proactively.

My state manager class is a singleton, and the paradigm makes extensive use of the Observer design pattern.

Works pretty well...
--RB

#14
04/05/2010 (5:21 pm)
I also have a game state kind of a thing, it controls how the enemies are generated etc. I could have combined them I guess but I thought this was the most portable for my purposes.

#15
04/05/2010 (6:03 pm)
I contributed an article on the topic of Game State Management using the Observer pattern to the book; Game Engine Gems.

The book was just launched at GDC last month.

Hit http://www.gamenginegems.com/ if you want to check it out.

My article is chapter 26; The Game State Observer Pattern.

--RB
#16
04/06/2010 (5:21 am)
That's pretty cool to use the Observer pattern. I am working on my first game and haven't designed the state management stuff yet, but I will now look into using what you described. Seems like a good way to do things.
#17
04/06/2010 (5:35 am)
While my current project is not my first game, it will be my first TorqueX game.

From what I can see, my state management method will blend in pretty well with TX. At the moment, I'm in early prototype stages, so my app only has 1 state. It starts and goes immediately into the game...I don't even have a pause feature at this point.

When I extend my prototype to have more than one scene, that's when I'll begin integrating my state management.

Good luck with your first title!
--RB