Jump to content

Suggestions how best to do this...


flysouth

Recommended Posts

Hi


I want to detect if the player gets near to an object that they can interact with, I have done some keyboard driven tests using

%result = %control.getLookAtPoint(%range,$TypeMasks::StaticShapeObjectType);
%obj = getWord( %result, 0);
%Message = "Looking at: " @ %obj.getName();
commandToClient(%client, 'ActionMessage', %Message);

and it works fine.

I now want to set a schedule to do this every 250ms for each player.

I see how the scheduling is done but if I wanted this to happen for all players on a multiplayer system, what would the best way be to set up the schedules?

Would this also work for single player games with client and server on same machine?

Link to comment
Share on other sites

hmmm I not tested on MP maybe you can do something like this


 

//file: game/scripts/server/player.cs
function PlayerData::onAdd(%this, %obj)
{
   // Vehicle timeout
   %obj.mountVehicle = true;
 
   // Default dynamic armor stats
   %obj.setRechargeRate(%this.rechargeRate);
   %obj.setRepairRate(0);
 
   %obj.schedule(250, "scheduledUpdate");  // <<=========== HERE
}

 

I not sure if is better to use ContainerRayCast to detect if the player is near to an object and check if the object is a staticshape, or if the object have a dynamicfield with getFieldValue("SOME_NAME") or something like that...

Link to comment
Share on other sites

Thanks for the reply. I have now run into a problem with setting the schedule. I can get it all working if I put the schedule in command.cs but I think it must be assigned to the player. So in PlayerData::onAdd I call my schedule function for the first time. But when I re-call the schedule from within the function it gives a message of scheduledLook: Unknown command. This error is coming from line 6 below....

This is the function:

function Player::scheduledLook(%this)
{
	echo("schedule called");
	CommandToServer('ActionLook');
	cancel($ActShedId);
	$ActShedId = schedule(250,%this, "scheduledLook");
}

 

okay I have got it to work by changing the function to this:

 

function Player::scheduledLook(%this)
{
	echo("schedule called");
	CommandToServer('ActionLook');
	cancel($ActShedId);
	$ActShedId = %this.schedule(250, "scheduledLook");
}

 

According to my understanding of the wiki they should both work....

http://wiki.torque3d.org/scripter:schedules-and-timers

Link to comment
Share on other sites

The first doesn't work because the function is defined as Player::scheduledLook(%this). That means it's a method (thing) of the Player class (namespace thing). So in order to be called you need a player (%this is the player in your working code so %player.scheduledLook() is called after the 250 ms using that %this.schedule(...)).

For it to work in the first way (plain schedule without player) it should be defined as

function scheduledLook() { ... }

as a global function.

Link to comment
Share on other sites

For it to work in the first way (plain schedule without player) it should be defined as

function scheduledLook() { ... }

as a global function.

Why? The first method passes %this to the scheduling code. Surely the intention of the second parameter is to pas the object to the scheduling code so that it can call the method? If this is not the case then why do we pass that object?

If you look at the link provided, I used it the same way they did in their example.

Link to comment
Share on other sites

Ah, there is a small difference on how both schedule work. They're not the same.

EDIT: Hmm, I think that reference is wrong.


In short:

···schedule(250,object,"aaa") calls function aaa() as long as object exists. If object is deleted then the schedule stops. It's used just for that condition.

···object.schedule(250,"aaa") calls function object.aaa(%this) if object exists. The function has access to object using %this.


They call different functions.


If you want to test, try to create both functions and use the different types of schedules...

Copy-paste this in the console:

function Player::hello(%this)
{
  echo("This is with the Player.schedule(250,\"hello\")");
}

function hello()
{
  echo("This is with the alone schedule(250,Player,\"hello\")");
}

 

And then use:

(LocalClientConnection.player).schedule(250,"hello");

and

schedule(250,LocalClientConnection.player,"hello")

Link to comment
Share on other sites

Thanks for the example but....

If I change schedule(250,LocalClientConnection.player,"hello") to schedule(250,0,"hello") it still works. This seems to indicate that for the stand alone function it does not care about that object. From this I take it that the object was intended for use when the function was a method of an object such as in the example at the link provided above???

Link to comment
Share on other sites

%this is (as noted, a rule not always followed by the example code) the nomenclature used for the first entry in a callback, typically referencing the attached simobject's ID. for datablocks that means the datablock, for object instances, that means the particular instance. Why you'll see quite a few callbacks with somethinseomthingBlock(%this,%obj){dostuff}. formers the shared data, latter is the per-object data.


Our end what we do is tack on triggers programatically so if nobody is around an interactive object we save some load, but if you wanna iterate connections:


function myfunc()
{
if(isObject(ClientGroup)) //are we using the default sample client connection group?
{
	%clientCount = ClientGroup.getCount(); //how many
	for (%clientIndex = 0; %clientIndex < %clientCount; %clientIndex++)
	{
		%client = ClientGroup.getObject(%clientIndex);//get the id from the vector-offset
		%obj = %client.player; //get the player body reference set in the (stock sample) camecore.cs *
		//(insert castray codez here)
	}
}}

 

*https://github.com/GarageGames/Torque3D/blob/development/Templates/Full/game/scripts/server/gameCore.cs#L939


You could also start a schedule in a player::onAdd, that reschedules itsself:

function PlayerData::onAdd(%this, %obj)
{
%milliseconds = 1000;
%somevar = true;
%obj.schedule(%milliseconds,"myMethod",%somevar); //note here it's obj
}
function Player::myMethod(%this,%passedvar)
{
%milliseconds = 1000;
%somevar = %passedvar;
%this.schedule(%milliseconds,"myMethod",%somevar); //and here it's %this
}

Link to comment
Share on other sites

@Azaezel Thanks for the explanation. I have already done it in the PlayerData::onAdd, so I am glad to see that you have suggested that too. I assume that this will then work for each player that joins the game? At some stage I will set install it on another PC to test this.

Link to comment
Share on other sites

Yes, though a word of caution as a general pragmatic note: aiplayer derives from player, so be careful of inheritance there, as well as slamming the system with too much code going off simultaneously by spawning a bunch of stuff at once leading to heavy load going off at near the same time. Why I favor stuff like https://github.com/Azaezel/Torque3D/tree/AI_manager for smoothing out execution.

Link to comment
Share on other sites

...

Our end what we do is tack on triggers programatically so if nobody is around an interactive object we save some load,

...

 

Dude, you wouldn't by chance mind giving an example of that "tacking on triggers so if nobody is around" bit would you? I am in the middle of constructing a custom AI system in source and it's using signals. I was wondering what the best way to handle the 'tick' is? I'm trying to determine whether or not I should have a custom 'Manager' class handle all that in its 'tick' functions or if I should have the clients gather the local information to see if a signal should be triggered? From what I've managed to gather so far, it's looking kind of like both...the client gets the stuff in range and the custom Manager class could use that information to trigger those in range.


@flysouth : Are you trying to keep all of this in script? I have grown partial to using the ScriptTickObject class for having stuff 'tick' in script. You could have the ScriptTickObject call whatever is dynamically being refreshed, but I'd be careful using this approach. The good thing about the ScriptTickObject is you can throttle back the speed it ticks so whatever script you're feeding into it doesn't get called a bajillion times a second. I'd recommend a bit of research around that class. Also IIRC to get the ScriptTickObject to 'start up' you have to 'turn it on' using a script function. Can't remember the syntax off the top of my head, but if you decide to use the ScriptTickObject and need help just ask. I have at least one good example of it on my hard drive someplace.

Link to comment
Share on other sites

Just skim read the thread but off my head ...

 

Thanks for the caution. At the moment I am just using it for basic things such as opening doors etc.

 

You could slap a trigger over the door area (making it larger so the player doesn't run into the door to have it operate) and have it operate that way with the trigger's onEnterTrigger function. Probably a good idea to have it contain a variable for open or shut (isOpen) and have the door object ID (doorID) given to the trigger. But the gist should be something like.

 

function doorTrigger::onEnterTrigger(%this,%trigger,%obj)
{
   %door = %trigger.doorID();
   if(%trigger.isOpen == false)
      if(%obj.getType() $="Player")//only players open doors, note AIPlayer derives from Player
         %door.playThread(0, "openDoor");//your animation for the door or call whatever function that opens it here
}

 

Alternatively you could have the player touch the door on the Player's OnCollision callback.

function Player::onCollision(%this, %obj, %col)
{
   // assuming door is staticObject
   if (%col.getType() & $TypeMasks::StaticObjectType)
   {
      %db = %col.getDataBlock();
      if (%db.getClassName() $= "DoorData" ) 
      {
         // Open door
         %col.playThread(0, "openDoor");//for animation or whatever function call to open door
      }
   }
}

 

Anyhow those or all things which can help reduce using looping schedules.

Link to comment
Share on other sites

@Azaezel : Ah okay, yea, I gotcha. That's actually close to what I was doing in script when I first put all this AI system together. I mean the way you guys are adding the triggers onAdd(). I can get all my signals firing if I add a trigger for each signal I want to fire, easy peasy. What I'm actually trying to achieve at this point is a tad more advanced. I'm moving that trigger based scripted system into source, and what I'd really like to see happen is have the client detect what's in range and THEN fire the signal. This way, although all of the potential objects may be registered to the event, only those in range of each client will actually fire the event. Tbh I'm fairly close to nailing it, of course I'd be glad to share when it's complete.


@flysouth : Well, if you have a schedule running it's full-on running as often as you tell it to. With a trigger, it can just sit idle and do nothing until something is within its bounds. Of course, regardless of what you do there is going to be a 'listener' of some sort paying attention to what's going on to fire off the event.


I must be too deep in my current implementation, I didn't completely address your question flysouth. I'd recommend you avoid triggers/scheduling altogether.


What you should do is have your event(the moving of the door/collision) fire off after a successful raycast. So shoot a ray at the door from your player's eye and once it hits, fire off the event that handles the animation of the door/collision shape.

Link to comment
Share on other sites

What you should do is have your event(the moving of the door/collision) fire off after a successful raycast. So shoot a ray at the door from your player's eye and once it hits, fire off the event that handles the animation of the door/collision shape.

 

I am scheduling a raycast evry 300 ms to look in front of my player to see what the player can interact with. I dont want them to collide with door to do this. When a door is detected an option to unlock or open the door is shown in a HUD. They can then decide to open it if they want.

According to the T3D doc the trigger tick is every 100ms so it might have more overhead than my schedule raycast every 300ms???

Link to comment
Share on other sites

According to the T3D doc the trigger tick is every 100ms so it might have more overhead than my schedule raycast every 300ms???

 

Okay that's a bit clearer, I see what you're up to now. You want that dynamic GUI dialog to automatically pop up. I took it to mean you wanted to walk up to the door, hit a button, and the door opens(no popup involved).


Well in any event constantly firing a raycast out in front of the player detecting stuff will surely keep things 'ticking', although I'd still approach it just a tad differently to reduce any additional overhead. Consider that the player may be out in the wilds someplace, far away from any potential door to be opened, yet he continues to cast rays out looking for doors that couldn't possibly exist. Here I'll try to address your implementation to the best of my understanding:


Triggers :

From old documentation :

The defaultTrigger datablock defines its tick frequency at 100 ms.

So here we see that you can create your own trigger datablock, and instead define its tick frequency to whatever you want(i.e. 300ms that you currently use while raycasting). Alright, great, so you can use triggers at the same tick rate - how does that help?? Let's take a closer look at the Trigger class again.


In the same link provided above, check out the function DefaultTrigger::onTickTrigger (near the bottom of the page, also referenced here.) What this field allows you to do is enter in a command that you want to be executed while objects are inside the trigger. So, not only can you have your raycast function be called by the trigger at the same rate, you can also have it only fire off the raycast search while the player is close enough to the door for it to matter.


Meaning you can create a trigger area much larger than just a few feet across, say maybe 10 feet x 10 feet, and have that trigger call to your player's raycast function only while the player is within that range of the door. Now you have the same functionality you did before but it's a LOT more efficient.


Now, to be completely clear about the Trigger class, it should be understood that the Trigger itself will continue to tick at the rate you set it to even if nothing is in the Trigger's area. However, and this is the important part; the Trigger class's onTick() source code will immediately return and won't execute any code unless the criteria you set for the Trigger are met. So it will continue to 'listen' but it won't execute the raycast search code unless the player object is within the bounds of the trigger.


Disclaimer:

As usual bear in mind of course there are many ways to approach any given problem(be it in source or in script). For what you've got going, this seemed fairly appropriate, although I can see other paths as well if it isn't optimal to your needs. Personally I've spent a good deal of time and planning avoiding as many triggers and schedules as possible, but in some cases you just have to use the available classes to the best of your ability or craft up new ones :lol:. Think it over, see if it helps, cheers!


P.S.

Another thing to consider is that you could avoid the raycast code altogether. If you are going through all of that just to get a GUI control to pop up you might as well place a small trigger area in front of the door. Then when a player hits it, the GUI control pops up indicating the player is close enough to open the door. If the gameplay is of a type where clicky GUI navigation is happening to interact with stuff, whether or not the center target reticle is 'looking at' the door shouldn't matter. If you are standing on the door square you can open the door. Food for thought =)

Link to comment
Share on other sites

@TorqueFan Thanks for all the detail. It has given me some ideas and plenty to think about.

 

So it will continue to 'listen' but it won't execute the raycast search code unless the player object is within the bounds of the trigger.

My understanding that the trigger itself works by doing a raycast of the trigger area??

Looking at this would it not depend on how many items the player can interact with vs how many players there are? If you have 1 player with say a 100 doors then you only have one raycast doing it from the player vs 100 doing it from trigger events?

Link to comment
Share on other sites

@flysouth : That's a very good question, but likewise there is good news in return. I just cracked open some of the source code to be sure we are on the right page and uncovered just a tad more detail about Triggers.


mObjects

The first thing to understand is the Trigger class itself keeps track of a list of objects within its bounds, called mObjects. The important thing for us to figure out is exactly how the list of objects is updated. Luckily the Trigger itself is not constantly performing searches within its entire area even when there are no players inside. There is a function in the trigger.cpp file that will get the information from the GameBase object that hits the Trigger. The relevant function is potentialEnterObject(GameBase* enter). One good thing about it is it already takes care of client/server side stuff!


In trigger.cpp :

void Trigger::potentialEnterObject(GameBase* enter) { ... }

 

This means that the Player object determines when there is a collision with a Trigger or not. The best part?? The Player already performs this check by default right now, so every test you've ever run already had the Player object searching for Triggers while moving. This is found in the player.cpp file in the findContact() function. Here's the relevant bit:


In player.cpp :

void Player::findContact( bool *run, bool *jump, VectorF *contactNormal )
{
...
     if (objectMask & TriggerObjectType)
     {
        Trigger* pTrigger = static_cast<Trigger*>( obj );
        pTrigger->potentialEnterObject(this);
     }
...
}

 

What this is doing is it is determining if the contact object is a Trigger or not. Then if it is it's 'getting ahold' of the Trigger object by creating a pointer. Lastly it uses that pointer to tell the Trigger object that there now is indeed a GameBase object that is within its bounds - a Player no less! The Trigger will test that object again( in the potentialEnterObject() ) function I posted above) and if it meets the criteria it will accept that there is a GameBase object within its bounds.


Now that the Trigger object is equipped with a proper GameBase object to add to its mObjects list it can finally begin to execute any of its code. As you can see, the Trigger really does pretty much sit around idle and do nothing at all until there are GameBase objects within its bounds and on its list!


I hope this helps further, tbh I learned a tad bit more about the contact code myself! Good luck, happy Torquing!


Notes:

Note that you still have to weigh for your game what's the biggest hit. The usefulness of one method or another will vary based on number of doors vs. number of players and so on. All of what I've described here is for the greater good of actually answering the question, "How best to do this?", although by now it should be clear that "best" is specific to the implementation. To truly get the answer I'd suggest giving either way a shot and profiling the results to find out exactly what's more efficient. Personally I usually design/develop classes and objects with multiplayer in mind at all times, so I tend to use approaches that can 'handle' high traffic scenarios.

Link to comment
Share on other sites

I found this information, which is the approach I would probably use:

StaticShape solution by Konrad Kiss


So you could add a collision shape in the model file that encompasses the area where you'd want the GUI popup to appear. The player hits that area, popup the GUI. If they confirm to open the door, setMeshHidden() on the collision shapes and animate the door.


This looks like the most elegant solution, although I hadn't scripted any of it to test. Long ago I did something similar with a garage door and it did indeed animate/kill the collision.

Link to comment
Share on other sites

mmm interesting. I went to the page where Konrad Kiss writes about using collisions and just a bit further down Bryan Sawler makes this statement

A ray-cast will be, in most cases quicker, and any kind of complex object (convex hulls and up) generally use multiple ray-casts in their collision determination

So we are back to racasts lol.


But in your look at the code it appears if the player is constantly doing a raycasts to see if it collides with a trigger for example. If this is the case using the trigger might be better because you can take advantage of something that is happening already, It does make you wonder if you use script to do a player raycast does it just use the data that is already being gathered by the ray cast that the player is always doing?

Link to comment
Share on other sites

A ray-cast will be, in most cases quicker, and any kind of complex object (convex hulls and up) generally use multiple ray-casts in their collision determination

 

That statement compares a single raycast vs. multiple raycasts occurring for a single collision determination.


We are comparing a single raycast being cast(300ms) constantly vs. multiple raycasts occurring for a single collison determination only when the player hits the door's collision area.


One way to look at it is like: Do you want the engine constantly idling in your driveway, or would you prefer to only start it up when you need to drive it?

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...