- Home /
A List of functions to call - is there a better solution?
Hi to all!
In my game, I need the consequences of player actions (i.e. which functions are triggered) to be highly customizable. I had started out like this (abstract example, ResponseModes is an enum):
function PlayerTappedTarget()
{
if(responseMode == ResponseModes.Nice)
{
TellPlayerSomethingNice();
score++;
}
else if(responseMode == ResponseModes.Antagonist)
{
TellPlayerSomethingNasty();
BlockControls();
score = -1000000;
}
else if(responseMode == ResponseModes.None//and so on
{}
}
The problem with this approach is that I need lots of ResponseModes, and it seemed a bit clumsy. So I'm trying another approach:
enum Responses
{
TellPlayerSomethingNice,
IncreasePlayerScore,
TellPlayerSomethingNasty,
DecreasePlayerScore,
etc...
}
public var tapResponses : List.<Responses>; //defined in the inspector, may be modified at runtime
function PlayerTappedTarget()
{
DoResponsesList(tapResponses);
}
function DoResponsesList(responseList : List.<Responses>)
{
for(response in responseList)
{
switch(response)
{
case Responses.TellPlayerSomethingNice:
TellPlayerSomethingNice();
break;
case Responses.IncreasePlayerScore:
score++;
break;
//and so on
}
}
}
Is there a more elegant way to do this? I have about 25 different Responses, in case...
Thanks in advance for your insights!
Answer by Sirex · Apr 12, 2012 at 03:58 PM
Yeah i would consider making a design where you don't have the logic in the loop. Have the logic predefined and just call it in the loop.
Rough idea.
enum ResponseEnum:
responeNice
responseNasty
public CurrentResponse as ResponseEnum = ResponseEnum.responseNice
struct ResponseStruct:
response as ResponseEnum
codeBlock as callable
OR // the OR is not code it is psedo programming
// for showing an alternative
functionList as List
code1 as List = [TellPlayerSomethingNice, { return { changeScore(1) } } ]
OR
code1 as callable =
{ TellPlayerSomethingNice();
changeScore(1);
}
responseStruct1 as Response = Response(response: ResponseEnum.responseNice,
codeBlock: code1 OR functionList: code1)
_responseHash = { responseStruct.response: responseStruct1}
def PlayerTappedTarget():
ExecuteResponse(CurrentResponse)
def ExecuteResponse(responseStruct as ResponseStruct):
correctResponse = _responseHash[responseStruct.response]
for functionCall as callable in correctResponse.functionList:
functionCall()
OR
correctResponse.codeBlock()
Hope yon understand a little of what i am trying to say :p.
A little, but alas I'm not familiar with C# ! It's unfortunate, I see you put some time in your answer. Is my way of doing things clumsy because of the switch case the logic has to go through every time?
This is describing how to create a list of functions. It's a tricky concept at first (Freshmen at $$anonymous$$IT learn it, but for most other people it's one of the last things they learn.)
One of the things it's good at is adding/changing what an action does at runtime.
Sorry for late answer, was training bjj. Your way of doing things is "ugly" from an design perspective because your loop is hard coded. With my way you only have to add things, in my example ResponseStructs, to a list that that the loop go through. Correct me if i am wrong, but your code actually don't achvie adding things add runtime since all your cases and if statements are hardcoded? With my way you can add things at runtime since the main loop process a list that can be modified.
Tricky and tricky, basic functional program$$anonymous$$g. This is written in Boo which runs in unity3d. If you need it fast you could try my script, otherwise we need to convert it to javascript. Yes it is doable in javascript :)!
This works, tried both ways in unity3d, the code gets formated funny on this site but the code is useable. #pragma strict var _score : int = 4;
var CurrentResponse : ResponseEnum = ResponseEnum.responeNice;
var _responseHash : Hashtable = new Hashtable();
function Start () { PlayerTappedTarget(); }
function Update () {
}
function TellPlayerSomethingNice (){ print("Hello"); print(_score); }
function addScore (newValue : int){ _score += newValue; }
enum ResponseEnum { responeNice, responseNasty }
class ResponseStruct extends System.ValueType {
var response : ResponseEnum;
var functionList : Array;
public function ResponseStruct(response:ResponseEnum, functionList:Array){
this.response = response;
this.functionList = functionList;
}
/*
OR
*/
/*
var codeBlock : Function;
public function ResponseStruct(response:ResponseEnum, codeBlock:Function){
this.response = response;
this.codeBlock = codeBlock;
}
*/
}
var code1 : Array = new Array(TellPlayerSomethingNice, function() { addScore(1); });
//OR
/* var code1 = function()
{ TellPlayerSomethingNice(); addScore(1);
}; */
var responseStruct1 : ResponseStruct = new ResponseStruct(ResponseEnum.responeNice, code1);
_responseHash.Add(responseStruct1.response, responseStruct1);
function PlayerTappedTarget(){
ExecuteResponse(CurrentResponse);
}
function ExecuteResponse(currentResponseEnum : ResponseEnum){
var correctResponse : ResponseStruct = _responseHash[currentResponseEnum];
for (var functionInList : Function in correctResponse.functionList)
{
functionInList();
}
// OR
//correctResponse.codeBlock();
}
Got yield function to work. var code1 : List. = new List. ();
code1.Add(TellPlayerSomethingNice);
code1.Add(function() { addScore(1); });
code1.Add(function() { WaitForSeconds (6); });
code1.Add(function() { StartCoroutine("TellPlayerSomethingNice"); });
...
loop code
for (var functionInList : Function in correctResponse.functionList) {
yield functionInList();
}
When this function was called in the loop it waited the 6 seconds.
Answer by SirGive · Apr 12, 2012 at 05:24 PM
why not use classes and virtual functions?
You could make a base class called response and make each child a different response.
here's and example I've done (virtual and override are implicit in unityscript):
Declaration (you could even define these in a separate script file):
public class UpgradeBase
{
protected int curSelection = 0, prevSelection = 0;
protected string description, title;
public List<GameObject> mesh;
protected Selection type = Selection.None;
public virtual void UpdateSelection(int _var) { }
public virtual void SelectionLoop() { }
public virtual void RenderUpdate() { }
public virtual void Reset() { }
public virtual int GetSelection() { return curSelection; }
public virtual void SetSelection(int _selection) { curSelection = prevSelection = _selection; }
}
public class TurretUpgrade : UpgradeBase { protected Selection type = Selection.Turret;}
public class MachineGunUpgrade : UpgradeBase { protected Selection type = Selection.MG;}
public class PaintUpgrade : UpgradeBase
{
protected Selection type = Selection.Paint;
public override void Reset() { }
public override void RenderUpdate() { }
public override void SelectionLoop() { }
}
public class OptionalUpgrade : UpgradeBase{
public override void SelectionLoop(){}
public override void RenderUpdate(){}
}
public class ArmorUpgrade : OptionalUpgrade { protected Selection type = Selection.Armor;}
public class WeaponUpgrade : OptionalUpgrade { protected Selection type = Selection.Weapon;}
public class ExhaustUpgrade : OptionalUpgrade { protected Selection type = Selection.Exhaust;}
basically, you define you base attributes. Then you change them to be overridden for you case. Create a list of your base class:
public List<UpgradeBase> upgrades = new List<UpgradeBase>();
Here's instantiation of the elements into your list:
upgrades.Add(weapon);
upgrades.Add(armor);
upgrades.Add(skin);
upgrades.Add(turret);
upgrades.Add(mg);
upgrades.Add(exhaust);
then you can call one function like this:
public void UpdateSelection(Selection _selected, int _var){
upgrades[(int)selected].UpdateSelection(_var);
}
Just make sure the order you add them to the list matches your enum:
public enum Selection { Weapon, Armor, Paint, Turret, MG, Exhaust, None }
(please excuse a few inconsistencies in the naming)
Might be a bit overkill, but it makes adding new elements and sub elements a lot easier.
@SirGive Thanks for your contribution, this thread is starting to look interesting! The docs say classes are to be avoided when targetting mobile devices, comment? Also, as I wrote above, so far so good with my code. To change the response to a particular user action, I just do (abstract): tapOnObject1Responses = new List.([Responses.DoThis,Responses.DoThat]); So, is there something really awful about the way I'm doing things? $$anonymous$$y main concern is performance. $$anonymous$$aintenance wise, it's quite convenient as it is... Already thankful for the opportunity to learn, +1.
I actually did not know classes were suggested to be avoided. The code above is from a mobile app currently in development, though this system will have very broad usage. I wouldn't say your route is awful, as that's how I first structured the system above. Its just now the script that held the massive switch case only contains 2 or 3 very small functions. And adding/modifying elements took a few $$anonymous$$utes ins$$anonymous$$d of copying a whole case statement
@SirGive FYI : http://unity3d.com/support/documentation/$$anonymous$$anual/iphone-Optimizing-Scripts.html under "Avoid allocating memory"
Thanks again for your time!
Gregzo
@gregzo: that link doesn't actually suggest avoiding classes. It says you should try to use structs ins$$anonymous$$d of classes where possible, but I'd have to say "where possible" is a pretty narrow range, since structs and classes are not really all that interchangeable even if they look similar at first. At least they note the additional overhead structs have if they're abused.
Answer by Eric5h5 · Apr 12, 2012 at 08:52 PM
It sounds kinda like you'd want to use delegates? Specifically, in this case, an array of functions that matches the enum:
enum Responses
{
TellPlayerSomethingNice,
IncreasePlayerScore,
TellPlayerSomethingNasty,
DecreasePlayerScore
}
public var tapResponses : List.<Responses>;
private var functions = [
TellPlayerSomethingNice,
IncreasePlayerScore,
TellPlayerSomethingNasty,
DecreasePlayerScore
];
function DoResponsesList (responseList : List.<Responses>)
{
for (response in responseList)
{
functions[response]();
}
}
function TellPlayerSomethingNice () {
Debug.Log ("You're nice!");
}
function IncreasePlayerScore () {
score++;
}
function TellPlayerSomethingNasty () {
Debug.Log ("You stink!");
}
function DecreasePlayerScore () {
score--;
}
The important thing is that the list of functions matches the order of the enum, not that the names are the same. Doing "`functions[0]()`" will run the first function in the functions array, for example.
Thanks Eric, I didn't know I could declare an array of functions! I still have 3 issues with this solution : 1-If one of the functions in the array contains a yield statement, it will simpy not run when called (no errors, nothing). 2-I tried yield functions[0](); not possible to evaluate an expression of type void... Even if the function is defined as a IEnumerator. 3-When declaring the functions array, putting a function from another script in there gives me a Null Reference. Is there a workaround besides using a "relay" function? $$anonymous$$any thanks for your help!
The type (in the above example) is function()[]
. 1/2) Functions with yield are coroutines, and they return IEnumerator rather than void. So you couldn't mix those two types of functions together in the same array, since all delegates that are called the same way need to be the same type. Also, in order to use coroutines as delegates you have to explicitly use StartCoroutine. 3) You need to use GetComponent. i.e., if ScriptB has a function "SomeFunction", then ScriptA can do
var myDelegate = GetComponent(ScriptB).SomeFunction;
myDelegate();
Thnaks! I was just reading about the Function type as you replied. Capital F, no? var myFunctions : Function[]; 1/2) Oh no! The (ugly) way I'm doing things for the moment, I can control a sequence of timed events in my responseList, which is very handy in my case. To make it work with delegates, I would need to group my coroutines in a seperate array, and in the DoResponsesList(), check which array of functions response belongs to, correct?
No, not capital F. It should be "`var myFunctions : function()[];`". As for coroutines, yes, that should work. It wouldn't be quite as nice but I guess two arrays is better than 25 if/thens.
Sorry for being a bugger, but wouldn't it be really painful to maintain? Let's say I declare myFunctions and myCoroutines, the Responses enum wouldn't match anymore. I could have empty slots in one of the arrays, but that would be ugly and a sure source of index mistakes. Plus I need 3 types of responses : calling a function, or a coroutine, or yielding a coroutine... I guess I could create a responseTypeList matching the responseList, but it's all beco$$anonymous$$g quite cumbersome!
Answer by Bampf · Apr 12, 2012 at 03:00 PM
Instead of calling a method like "IncreaseScore()" you can send your game object the message "IncreaseScore". The receiving game object handles the message by having an actual method of that name.
Have a look at the docs for SendMessage to see what I mean.
This eliminates the need for your switch statement. You can even include one parameter with the message, though none of the example responses you posted have any arguments so maybe you don't need that.
(Edit: For this scenario I would recommend using the RequireReciever option, to avoid messages failing silently due to typos.)
I'm avoiding Send$$anonymous$$essage() as it is slow and uses strings (app is for iPad). Plus, I don't have a method called IncreasePlayerScore... Thanks for the reply anyway!
Statements like "don't use Send$$anonymous$$essage on mobile devices" are not hard rules. Unity's own mobile tutorial project, Penelope, uses Send$$anonymous$$essage. While you wouldn't want to have dozens of objects sending messages to each other every frame, sending half a dozen messages when the user taps on something is not going to have any noticeable performance cost.
Another example: people "know" to avoid UnityGUI on mobile devices, but plenty of published games actually use it, including $$anonymous$$e. The trick is simply not to use it during moments of actual gameplay where FPS is important.
In your shoes I would start using Send$$anonymous$$essage at first. As you can see it's far simpler to use and maintain than the strongly-typed suggestions, and you are of couse free to replace it with a more elaborate dispatching method if it becomes as a performance bottleneck. $$anonymous$$y guess is it won't, but it depends on your game. You want to spend your time optimizing where your game is actually spending most of its time.
@Bampf Thanks for your comment! I didn't on my first read get the implication of $$anonymous$$att's answer (that my tapResponseList could then be a list of strings...). But my app is a rythm tapping app, so framerate is pretty crucial, and one tap might involve as much as 10-15 responses, some of which are yielded coroutines, some coroutines, some functions, and a few require more than one parameter. I'm no stranger to Send$$anonymous$$essage, and interested in learning about more elegant ways to do things! I confess that so far, I've kept my 25 cases long switch: adding a new response is just a matter of adding a new item to the enum Responses and a new case to the DoResponsesList function, and it allows me to modulate the system's reaction to user input by simply changing the responsesList, which I can do in real time in the inspector. Quite handy for trying things out... I've learnt a lot on this thread, and will surely use the knowledge gained soon!
Fair enough! Every game is different, and you certainly know your game's needs better than I do. Send$$anonymous$$essage solves this problem conveniently, and is good enough for some situations, but perhaps not yours though. Good luck!
Answer by Owen-Reynolds · Apr 12, 2012 at 05:08 PM
You might be able to break down your responses into a series of small actions. For example, they all have a score they add (possibly 0,) a list of strings they randomly say, and each may or may not: kill you, destroy what you hit, ... . So (this is C#, but same idea):
class responseType {
int scoreChange;
bool destroyObjectHit;
int damageToObjecthit; // unused if destroyOH is true
bool causesMyDeath;
string[] randomResponses;
}
Then you just say score+=ReponseArray[responseMode].score;
and so on. Any one-off stuff (like responseMode13 teleports, but nothing else ever will,) you can use the switch. In other words, the switch is only for a few weird things.
This would allow you to modify a respose, or "disable" it with an extra bool.
Thanks a bunch! But is my method(2nd one) gloriously inefficient? So far, it's working fine. I'm just wondering if having to go through a 25+ switch case every time the user taps the screen might be an issue...
@Owen And I really need to adapt Responses a lot... Your idea is nice, but wouldn't I have to check all the instance's bool's to call the relevant functions?
The problem is 25+ switches/ifs can become 50, 100... and become impossible to upgrade. If 25 ifs works, leave it. If you plan to grow more, may as well get a better way now.
Yes, this trick has things like if(R.dostroyOnHit) killenemy(e1);
. But, you don't need it in a switch statement. you grab the single responseType
that matches (use an array lookup, or search the list) and do whatever it says.
You can also combine with the function-variable trick by giving responseType
one more "miscFunction" variable, usually null, to handle the odd stuff.
But, again, the trick is only good if responses are mostly picking what to do from a small menu.
@gregzo I wouldn't worry about going through, at worst, 25 cases in a switch when a user touches the screen. One of the games I'm working on now at work uses a switch statement to deal with touches, then tells all input-receiving controllers in order that a touch occured and where; the first one raycasts, picks up colliders on its layer, checks the texture coordinates it hit to see whether or not what it hit was actually transparent, if it was then try the next collider and do the same again, etcetcetcetcetc... So don't worry about 25 switches :) In fact, I wouldn't be surprised if the switch statement was actually faster than the if statements...
Either of your original ways works? Then stick with whichever one you have now. It will probably be a pain a long time down the road, though, if you start having to add more of them and in multiple places... If I was just wanting to write something quick and didn't really want to spend too much time on it, assu$$anonymous$$g (not good, I know) it probably wouldn't change much until the product's finished, I would probably use a switch statement myself...
By the way, have you ever used the profiler? You can see in great detail how much time every method is taking. It's often very very valuable trying to pick up on slow code since by default they are sorted by execution time in descending order, so the prime culprits float right up to the top of the list. You will probably notice that it takes it far less time to check 25 cases of a switch than it does to do a single Send$$anonymous$$essage().
@Datael Thanks for your input! I use iOS basic still, planning to go pro very soon, need that profiler... I'm quite new to coding (about a year now) and eager to learn about all sorts of patterns. In this precise use case, taking into account all that was written here, I'm not sure I need to change my switch case for a more refined pattern. As I wrote to Bampf on the post above, my ugly switch is for the moment very convenient, all the more so if you tell me that a 25 cases switch is faster than a Send$$anonymous$$essage. The more sophisticated options exposed in this thread are all options I will take into account from now on, so cheers to all!
Your answer
Follow this Question
Related Questions
A node in a childnode? 1 Answer
Create Enumeration from List 0 Answers
If String is in List Then... 2 Answers
How to make a List or Array of functions with type Void 4 Answers