Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
1 capture
13 Jun 22 - 13 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
1
Question by gregzo · Apr 12, 2012 at 02:44 PM · listfunctionruntimeenum

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!

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

5 Replies

· Add your reply
  • Sort: 
avatar image
1

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.

Comment
Add comment · Show 13 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image gregzo · Apr 12, 2012 at 04:06 PM 0
Share

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?

avatar image Owen-Reynolds · Apr 12, 2012 at 05:13 PM 1
Share

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.

avatar image Sirex · Apr 12, 2012 at 07:42 PM 1
Share

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 :)!

avatar image Sirex · Apr 13, 2012 at 09:13 AM 1
Share

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

}

avatar image Sirex · Apr 13, 2012 at 10:36 AM 1
Share

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.

Show more comments
avatar image
1

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.

Comment
Add comment · Show 4 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image gregzo · Apr 12, 2012 at 05:52 PM 0
Share

@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.

avatar image SirGive · Apr 12, 2012 at 05:58 PM 0
Share

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

avatar image gregzo · Apr 12, 2012 at 06:05 PM 0
Share

@SirGive FYI : http://unity3d.com/support/documentation/$$anonymous$$anual/iphone-Optimizing-Scripts.html under "Avoid allocating memory"

Thanks again for your time!

Gregzo

avatar image Eric5h5 · Apr 13, 2012 at 08:05 AM 1
Share

@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.

avatar image
1

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.

Comment
Add comment · Show 5 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image gregzo · Apr 13, 2012 at 07:40 AM 0
Share

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!

avatar image Eric5h5 · Apr 13, 2012 at 07:55 AM 1
Share

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();
avatar image gregzo · Apr 13, 2012 at 08:08 AM 0
Share

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?

avatar image Eric5h5 · Apr 13, 2012 at 08:35 AM 0
Share

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.

avatar image gregzo · Apr 13, 2012 at 09:07 AM 0
Share

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!

avatar image
0

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.)

Comment
Add comment · Show 4 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image gregzo · Apr 12, 2012 at 03:04 PM 0
Share

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!

avatar image Bampf · Apr 13, 2012 at 04:13 PM 0
Share

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.

avatar image gregzo · Apr 13, 2012 at 04:38 PM 0
Share

@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!

avatar image Bampf · Apr 18, 2012 at 02:28 AM 0
Share

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!

avatar image
0

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.

Comment
Add comment · Show 5 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image gregzo · Apr 12, 2012 at 05:29 PM 0
Share

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...

avatar image gregzo · Apr 12, 2012 at 05:30 PM 0
Share

@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?

avatar image Owen-Reynolds · Apr 12, 2012 at 09:14 PM 1
Share

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.

avatar image Datael · Apr 13, 2012 at 04:57 PM 1
Share

@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().

avatar image gregzo · Apr 13, 2012 at 10:52 PM 0
Share

@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

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

9 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

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

Create an array of Vector3 from Editor script 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges