Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411512 Posts in 69376 Topics- by 58430 Members - Latest Member: Jesse Webb

April 27, 2024, 12:16:57 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Characters ai code organization (old school not neural)
Pages: [1]
Print
Author Topic: Characters ai code organization (old school not neural)  (Read 4995 times)
gimymblert
Level 10
*****


The archivest master, leader of all documents


View Profile
« on: February 11, 2023, 05:40:40 PM »

I want to create a an ai with finely customizable character. The ai should be able to handle generic behavior shared across all character, but have specific override for group of character called persona, and override for unique character behavior.

I thought about using an "if tree" with behavior tree pattern. Basically imagine it's a bunch of cards. Cards have a check, then a body of actions when the check is passed, cards can be nested as actions of other card, break() is a specific action that terminate the execution of the whole tree. But I'm stumbling on some issue.

Override for specific characters, could happen at any branch of the if tree, and it makes it harder to write code that separate specific and unique code, because a top level card can be specific to a character, but lower behavior be a mix of generic and specific, making it hard to untangle the two. It makes it hard to propagate change during dev, because number of behavior card can reach up to tens of thousands. That makes it that a lot of card would be identical variants save for a few actions, and updating them all would be a nightmare. On top of that, breaking the ai code into multiple files makes it harder to track the logic and debug specific vs generic behavior, especially when the tree becomes deep.

What architecture or programming design pattern you would recommend to cope with these difficulties? Specifically how to manage the explosion of card variants?
Logged

gimymblert
Level 10
*****


The archivest master, leader of all documents


View Profile
« Reply #1 on: February 14, 2023, 11:59:29 PM »

I was looking for advice from people who were in the same situation. I didn't explain the architecture, my problem is highly localized to the behavior variants problem.

The architecture is: a world manager that manage entities, a utility tree (UT) for perception (match concepts instead of actions) that query the world and internal state, a behavior tree like (BT) that sets "state variables", and a state machine that actually implement actions based on those vars. Only missing is inheritance, I couldn't figure out how to add it without exploding the maintenance cost and the solve the exposed problem. BT is great because it's an abstraction between a planner, a script and a state machine, but I separated the state machine to handle actions, it's simpler to handle temporal aspects (wait the action to finish) and make transitions more explicit. Same for separating UT and BT, it's convenience to follow the character logic better.

After staring at the problem for a while, I realize there is mostly 4 cases:

- Insertion: I insert behavior with card in the form of "isaX()" check, it append new behaviors in the action list.

- Inhibition: basically I prevent a card from firing off for certain character and is in the form of "& !isaX()" so when identifier X is detected, the card return false and the behavior is not started.

- Substitution: basically I need to replace actions and maybe checks, but keep subtree intact. Right now it's a bunch of inhibition checks for action in the list.

- Replacement: basically it's a whole new tree that bypass the behavior for another one. Typically implemented as inhibition, of the old tree and append of a new tree, with an "else" structure.

I haven't figured out how to abstract them yet, and I also have a lot of hardcoded interactions for multiple characters, I haven't figured out how to formalize that part yet. The key things seems that, if I put everything in an actual list, I can at least insert stuff to generic behavior without having the generic BT and the specific BT having to KNOW each other...
« Last Edit: February 15, 2023, 12:06:27 AM by gimymblert » Logged

[email protected]
Guest
« Reply #2 on: February 28, 2023, 03:46:31 PM »

I get confused by conceptual descriptions of AI state machines. I prefer to think in terms of objectives: What does a character want to do, and does the character always want to do it?

For an FPS it's easy: AI wants to kill your ass. All it has to do is pathfind to line-of-site and shoot until the clip is dry.

A game like Morrowind is a little more complex. Morrowind's humanoid characters have 2 common states and a number of less common states. Most characters are either neutral or in combat. If a character is neutral, it idles around and acts like a person going about its business. If a character is in combat, it usually fights, but less common and more interesting states are triggered if the character is at an extreme disadvantage.

All of this can be written in a single file in whatever programming language you use. My kill-your-ass FPS AI is just over 200 lines of C++ executed per frame. With a neutral state, the AI might be 400 lines (and harder to read). With all the complexity of Morrowind, it would be 500-1000 lines and split into multiple functions.
Logged
gimymblert
Level 10
*****


The archivest master, leader of all documents


View Profile
« Reply #3 on: March 02, 2023, 05:26:57 PM »

I get confused by conceptual descriptions of AI state machines. I prefer to think in terms of objectives: What does a character want to do, and does the character always want to do it?


Funnily that's exactly what my tree does, it's organize such that, going down the branch read like a sentence. Like

Ryan: 
hear(negative evaluation from his dad){perception layer}
-> react with anger {emotional layer}
-> make rude gesture {expression layer: broadcast data}
-> storms out the room {set pathfinding destination}
Logged

[email protected]
Guest
« Reply #4 on: March 10, 2023, 10:01:12 AM »

I get confused by conceptual descriptions of AI state machines. I prefer to think in terms of objectives: What does a character want to do, and does the character always want to do it?


Funnily that's exactly what my tree does, it's organize such that, going down the branch read like a sentence. Like

Ryan: 
hear(negative evaluation from his dad){perception layer}
-> react with anger {emotional layer}
-> make rude gesture {expression layer: broadcast data}
-> storms out the room {set pathfinding destination}

Too much indirection. I would probably code it like:

Code:
if(evaluation < 0.5){
playAnim(finger);
walkTo(hallway);
}

Below that would be lots of other conditionals. More structure would be nice, but then you confuse people who are used to thinking linearly.
Logged
gimymblert
Level 10
*****


The archivest master, leader of all documents


View Profile
« Reply #5 on: March 10, 2023, 01:58:14 PM »

I don't know, looks like pretty much what i'm doing, My example was high level
Logged

Action Tad
Level 0
**



View Profile WWW
« Reply #6 on: March 21, 2023, 06:27:47 PM »


What architecture or programming design pattern you would recommend to cope with these difficulties? Specifically how to manage the explosion of card variants?

I would use classes if possible.
In your example, the reaction itself could be a class,
some function could return that Ryan reacted with anger, if he made a rude gesture and storms out the room.
 
You could have a Reaction class, and a Character class for taking in evaluations and interacting.

Code:
var ryan = new Character();var dad = new Character();
var reaction = ryan.hearFrom(dad); //would cause dad.talkTo(ryan)
ryan.reactTo(reaction, reaction.negative ? hallway : room);

Character.reactTo = function( reaction, destination ) {


  if(reaction.self) { //for example dads reaction would not be self, it would be ryans reaction to what he said.
                       //so the hearFrom and talkTo methods would return a reaction with self flagged differently.
    if(reaction.negative) {
        this.gesture(-1);
        if(destination) this.walkTo(destination)
    }
    if(reaction.positive) {
         this.gesture(1);
         if(destination) this.walkTo(destination)
    }

  }


}


hearFrom(charac) {

  var reaction = charac.talkTo(this);
  if(reaction.evaluation() <= 0 ) {
    reaction.self = 1;
    reaction.positive = 0;
    reaction.negative = 1;

  }
  return reaction;


}

talkTo(charac) {

  //lets imagine textQue is an Array containing an integer denoting how positive and then the saying itself
  //[2, "Have a nice day Ryan", 1, "Your not cool Ryan", 1, "You should not go to your room right now Ryan", 4, "Here is your favorite food Ryan"]

  //and it just has each thing to say next in order

  this.displayText(textQue[this.next+1]);
  return new Reaction(textQue[this.next] - 1, textQue[this.next], 0);

}

Reaction(n,p,s) {

  this.negative = n;
  this.positive = p;
  this.self = s;

  this.evaluation = function() {

    return this.negative / this.positive;

  }
}

Logged
gimymblert
Level 10
*****


The archivest master, leader of all documents


View Profile
« Reply #7 on: March 22, 2023, 04:14:15 AM »

My code look roughly like that:

PSEUDO CODE
Code:
//Black board:

//(reference to global ai sets on initialization, hold world state)
AiManager Manager;

//hold all var and flags relative to interaction in the immediate sensing radius
Struct PerceptionBoard;

//character data such as persona, stats and other that can be modified, managed by  the 3 decisions layer and consumed by the action state machine.
Struct CharacterState;

void Update(){
    if (isActive & isLiving)
    
        updatePerception();
    
     //Kind of a Subsumption architecture
     //select the proper behavior with 3 "layers" of if tree
     //Idea proposed was to get the most complex one and find way to break it further in layers. By finding pattern to generalize.
        updateSchedule();
        updateEvents();
        updateReaction();
        
        //Actually do the behavior
        performActions()
        
        else if (!isLiving)
        updateDeath();
}

        
void updatePerception(){

        //Fallthrough pattern (concurrency: just DO don't exit, all node execute in order, order don't matter)
        GetPlayerDistance();
        UpdateDetection();
        //...
}

void updateReaction(){

        //priority pattern (exit on success)
        if( gotPushed() ) return;
        if( gotHit() ) return;
        if( gotAlarmed() ) return;
        //...
}

void updateSchedule(){

        //Complex tree of functions with various ALTERNATING ai pattern as "actions", really should be renamed to "decision units", will clear some confusion

        //behavior tree pattern (generally wrapped into function to resume to the proper node after exit,
        //if it helps legibility or same type of decision is repeated accross node)

        //sequence pattern (exit on fail)
        if(!function1()) return;
        if(!function2()) return;
        if(!function3()) return;
        //priority pattern (see updateReaction above)
        //fallthrough pattern ("concurrency": see updatePerception)

        //Preprocess units (Order matter, do stuff for nodes and units further down, generally before a nested node tree)
        function4();

        //Nested node, precise a specific steps into a sub selections,
        //node CAN follow behavior tree pattern too with return on fail or success
        //or breaking the whole tree traversal.
        if(BlackBoard.check1){//prefix node comments indicating the task
                //interleaving pattern implementing logic, in any necessary order
                //->decisions units as sequence units
                //->decisions units as priority units
                //->decisions units as fallthrough units
                //->decisions units as preprocess units
                //->decisions units as nested node units
        }//suffix comments telling how much line there is, should add number of units and nodes instead, now thinkig about it...

        //more interleaved units and nested node

        //Observation there is a lot of "preprocess units" in the actual code.

        //problematic and specific node/units (happen at all hierarchy level)
        if (functionOrCheck() && isNAMEcharacter) ...
        if (functionOrCheck() && !isNAMEcharacter) ...
        if (functionOrCheck() && isPersona) ...
        if (functionOrCheck() && !isPersona) ...
}


Now the reason I have a "if-tree" like above, instead of breaking it into smaller function or class, is because legibility. It keep contextual stuff close together. With code folding I barely look at 100 lines at a times at worst and at the same time, despite it having thousands of code. Thousands of line is a bit misleading, a dozen of behavior at 100 lines go easily in thousands.

Legibility issues is compounded by the fact I have a single 720p screen, Im' working in the literal version of the meme "meanwhile two screen at home:" that is split windows set tup, with working one on the left, references on the right.
-> modern IDE allows for fancy options to look around the code, like peeking defintion and all, which I really can't leverage due to cramp space, using everything in one file, using folding, has been the best workflow so far. Long flat list of class, methods, rules and others, especially dispersed on multiple file don't help. Plus navigating thousands of file is way worse than navigating thousands of lines in a single place, code folding exist to snip the size.

That said, I came to realize something lately, It's not a character behavior code, it's a SCENE behavior code. The equivalent would be branching dialog sequence, in other game, except of text you have whole actions and behaviors. Joint actions between character, options, are decided by gameplay events during phases of the sequence.

It change the perspective on how to organize the code. Some data currently attributed to character, aren't for the character, but describe the SCENE states or transition from scene to another. That is SCENE contains character's behaviors, but aren't character behavior themselves, they direct character, and thus need specific encapsulation. That's why there was many characters within some branch of behavior, because it's misnamed and miscategorized.

Let's look at a mocked bath "behavior", not using a joint character scene
assuming it's the yandere simulator game as a reference:

0. is triggered by being dirty events
1. go to communal bath
2. go to locker
3. change cloth to swimsuits
4. take the bath
6. go to locker
7. change back to normal cloth
8. exit the communal bath

This would constitute the basic behavior.

The true purpose of the bath scene isn't to make the character realistic, but to offer gameplay opportunity in the game of social stealth and manipulation, it's the "level". That is the Dirty events is created as opportunities to move or remove characters from a place to another place. For example, the locker allow you to tamper with character belonging and do action like put a letter, stole the phone, stole the cloth, plant evidences, etc ...)

The actual (mocked) code would be more structured like that,
the (isX) denote added "if-cards" to create variants per character/persona (not joint behaviors) :

0. is triggered by being dirty events

    (is rich girl: rage 1mn about the clothes, change mood to angry)

    (is fragilePersona: start crying, change mood to sad)

    (is nemesis: look around suspiciously, mood change to vigilance)

    (is idiot AND judgementalPersona nearby: judgemental.talk, play embarassed animation)

1. go to communal bath

    (is rich girl: call her bodyguard to follow her on the way)
    (is rich girl AND is arrived at communal bath: the guard get posted at the entrance, intimidate anyone passing nearby)

    (is nemesis AND arrive at communal bath: slowly push the door, scan the room, close the door carefully, tapped a bell to the door)

2. go to locker

    (is rich girl: lament the cloth)

    (is nemesis: open the locker carefully, double check the environement and inspect inside the locker)

3. change cloth to swimsuits

    (is rich girl: look at the mirror to flatter herself, change mood to normal)

    (is nemesis: tapped a bell inside the locker, pick the knife, close locker's door slowly)

    (is idiot: look at the mirror do funny face and poses for a while)


4. take the bath

    (is fragile: cry during bath)

    (is nemesis: submerge whole body to hide the knife)

    (is idiot AND time remaining is small: do hurry bath)

6. go to locker

    (is nemesis: removed the tapped bell from locker)

7. change back to normal cloth

    (is nemesis: removed the tapped bell from door, open slowly the door, scan the environement)

8. exit the communal bath

    (is rich girl: the guard follow her to next place)

MISC interruptions and branches.

    (is nemesis AND bell sound AND 3 < phase < 7: Speech("I was expecting you"), change to fight mode)

    Letter at locker:

        (is nemesis: dismiss and toss to trash)

        (is rich girl: mood set to angry)

        (is fragilePersona: mood set to scared)

        (is idiot: Mood set to concerned)

    Phone stolen: [...]

    Evidence planted: [...]

    Clothes stolen: [...]

    Player detected: [...]

end.


BUT that doesn't solve my problem, which is how to SEPARATE different characters specific actions (variants) from the basic action toward their own files.

Basically the question is, how all these all (isX) insertions goes to their own file and merge back with the SCENE file.


Especially when we have more complex scene where I have joint behavior between multiple character (example: reporting a murder in yandere simulator, scene has a character who see a corpse, report to a teacher, the teacher and the reporter goes to the scene, and react appropriately (scene can be cleaned before they come back)).
« Last Edit: March 22, 2023, 04:36:37 AM by gimymblert » Logged

Action Tad
Level 0
**



View Profile WWW
« Reply #8 on: March 23, 2023, 03:35:44 PM »

I see, it can be very difficult trying to program on a small screen.
You could have different Structs; CharacterState, CharacterLocation, CurrentCharacter, anything you can think of to group ideas would help to lessen the amount of if/else statements you will need. The way you laid out those 8 points is actually not so bad, it may take a little while to write it all out, but that as a plain if/else chain would work.
Logged
gimymblert
Level 10
*****


The archivest master, leader of all documents


View Profile
« Reply #9 on: March 24, 2023, 05:05:49 AM »

To be frank the number of if else was never a problem. Though if I can better encapsulate them that's still a win.

On the practical scope of a dev, which isn't linear and is highly iterative, If are simple insertion, you don't spend one chunk of time adding all if in one go, and the structure is rather easy to grasp and navigate. That mean that constantly modifying the tree don't create problem, finding problem is equally easy, because if encapsulate behavior neatly, which also mean they are easy to move around.

Basically you have a few recurring mistakes that can happen:

- wrong logic checks, if enforce strong ordering, so that's easy to localized, you can follow step by step in the branching and avoid part of the code you don't need to look at.

- wrong ordering, if encapsulate logic in their body, fold and copy paste to reorder, copy paste is generally frown, but that's not the same type of copy paste (the laws vs the spirit of the laws).

- wrong logic in the body, since it's easy to localize, and that logic are self contained such that the scope doesn't bleed outside the if body.

The real benefit of if tree is the ordering in a branching structure, it has the same benefit that function or class for encapsulating logic, less so for encapsulating data BUT you can cheat by leveraging Lambda functions to wrap the If block to get some locality of variable (but not permanence of variable like object, which mean each frame you lose data), and you gain the function to break out at any point of the If block without going out the entire tree.

Code:
    //Breakable node
    () => {if (check){
           //code
           if(condition) return; //break from the node
           //code
    }}

If tree are typically bad in other programming context, where a simpler alternative generally always apply, and the context (edition, iteration, scope) are different.



BUT I figure out something and reorganize the code into a new architecture where I break responsibility further, it was basically a scope issues, redefining scene as scope instead of character modify how we can potentially organize the code:

- The global ai manager is stripped from some responsibility and is merely the manager of sub module. It may hold global generic behavior, but no longer track agents.

- The social area objects, hold by ai manager. Basically they hold area based "scene" called "social practice", for example the school hold practice like going to class, practicing sports, doing club activity. Another social area could be the mall, where "going to class and doing club activity" do not make sense. This represent the basic level background simulation.

- A story manager, which hold the structure of scenes that implement the progression of plots based on player's action and character states. It might inject Scene in social area to perform.

- A character sheet object, that implement the basic data of a character, such as appearance, stats and specific animations.

- A character script objects, that encapsulate the character sheets and the scene override of that character. The motivation in separating with sheets, is to be able to reuse character in different story.

- An actor agents, there is no longer a character object, actor takes a sheets to define their appearance, the stats to perform actions, and animation to display behavior. They also have a set of generic behavior such as moving and playing animation and hold the mutable states of the character. But the behavior are now injected by higher structure such as story, character scripts and social area objects, this can be implemented in many way, but for now I just keep a reference to these structure and call their behavior function on the character's state. This mean put the character in a mall or school, at a certain story point, and it would behave accordingly without modifying the actor, also it makes editing (authorship) collected in a a few places instead of many character.
Logged

Action Tad
Level 0
**



View Profile WWW
« Reply #10 on: March 27, 2023, 03:14:55 PM »

But the behavior are now injected by higher structure such as story, character scripts and social area objects, this can be implemented in many way, but for now I just keep a reference to these structure and call their behavior function on the character's state. This mean put the character in a mall or school, at a certain story point, and it would behave accordingly without modifying the actor, also it makes editing (authorship) collected in a a few places instead of many character.

I think that is a very good way to begin to tackle the problem, as many things as you can not have to re-code or go back and add to, will make overall coding easier. I think you can link a method to a struct by passing the struct as the first param of the method.
If that is not something you've tried already it might help.
Logged
gimymblert
Level 10
*****


The archivest master, leader of all documents


View Profile
« Reply #11 on: April 01, 2023, 06:22:57 PM »

Quote
will make overall coding easier.
Not really?

I mean it does clarified the intention.

But now I have more spot to look for doing essentially the same thing, the same number of actions.
And the solution to the initial problem is still "forced".
It make coding easier if I were to work with someone else and share the code. In some way that's the intention, to find a framework to code these type of interactions and explain to people how to make that type of project, assuming the final project is interesting enough.
Logged

Action Tad
Level 0
**



View Profile WWW
« Reply #12 on: April 19, 2023, 01:50:27 PM »

And the solution to the initial problem is still "forced"

I was going to ask, it seems your using the C language. Are you constrained to C? Or is it just a preference?
If I were to try to tackle the many options I would definitely want to use a language that is more OOP friendly.
C# would be more comfortable and easier if your already used to C, and you would be nicely ready to compile it in Unity and do the graphics in Unity.
Logged
gimymblert
Level 10
*****


The archivest master, leader of all documents


View Profile
« Reply #13 on: April 19, 2023, 03:07:16 PM »

And the solution to the initial problem is still "forced"

I was going to ask, it seems your using the C language. Are you constrained to C? Or is it just a preference?
If I were to try to tackle the many options I would definitely want to use a language that is more OOP friendly.
C# would be more comfortable and easier if your already used to C, and you would be nicely ready to compile it in Unity and do the graphics in Unity.


It's unity lol, therefore c#

It's an archtecture problem, and more precisely a modularity problem, how far can I segregated deeply coupled system (by nature, not design).

For example I end up having 3 layer of character behavior (scene, behavior, actions) but dispersed across many other system (area practices, story events, generic behavior, character specific), with at least one system deeply coupled by nature (character specific overriding for generic, story, area practices).

To be frank the actual implementation matter not, like I had outline, an If tree IN THIS CONTEXT, had all the requirement to quickly edit code and manage complexity EXCEPT if I wanted to add/remove character (flexibility would work, but tracking dead code per character would be ahad), it's not like it affect the project so much it makes it impossible to realized, but as much as possible I think it should be avoided.

I'm not satisfied with current solution (dispersion) but the segregation make sense in a high level overview, if I were to share the project. It's also agnostic to implementation, if tree or chunked function or object or scriptable objects really do the same thing and are more or less easy to navigate through hundreds of pieces (which is where the segregation help a bit and where IF tree has an advantage, because a tree its both segregating/grouping by nature, the various system are just various branch conceptually).

Still if I want to remove or add a character, I still has to jump over various system, instead of various branch. The only gain was having a deeper abstraction with scene to group behavior.

In conclusion:

- I haven't solved (fully) the main problem, that is specific character behavior override, because it touch many aspects.
- it's an abstraction problem, not a code problem
- I solved another related problem that precise the abstraction on behavior (scenes)

- The way it's partially solved, is that generic action can be overrides by simply using an equivalent to the decorator pattern.
- Scenes are state machine, which give a better addressing to inject specific behavior (targeting the specific state of the scene)

EDIT:
Regarding OOP, the main advantage is that it encapsulate data tightly, but does it make it coding easier? It depend on the code and the modularity needed. Behavior by nature are simple thing, they can be described as state change.

The idea is that is OOP makes it EASIER to navigate the code, that is generally when task done by an object is self sufficient and interact little with the environement, and thus can be abstracted away, so the question is how does my case fit that?

Technically Behavior can be made as objects, and in typical game ai, that's the way to go, because behavior don't interact much, and character have shallow and generic interactions, such kill, die, chase, patrol, the interface is minimal and generally shared, most character would have hitpoint, movement speed, attaque level, etc... same when you make simulation, like the sims, everything is generic and you basically just modify stats to create variety, ie agressive (attack more than fle), etc because actions exist on a scale.

However if you make a story, there is a lot of small scale unique interactions and many unique to character or set of character, while there is a great deal of generic interface to have, what remain is a lot of exceptions. So the problem comes from how to deal with exceptions.

One way to deal with that is components, ie you have unique components you attached to specific behavior, but that assume there is absolutely no dependence to other system and that a common interface is possible. And even then, which is my case, you would want a way to insert all the specific components of specific character to the common core of the objects. That is to keep the specific out in their own structure, such as you can add that structure to the common core and every unique component find the right place to be. Which is the issues I haven't (fully) solved.
« Last Edit: April 19, 2023, 03:32:10 PM by gimymblert » Logged

gimymblert
Level 10
*****


The archivest master, leader of all documents


View Profile
« Reply #14 on: April 19, 2023, 04:02:25 PM »

Asking BING chat:

Quote
I see. It sounds like you have a good understanding of the advantages and disadvantages of OOP for your case. I can see how behavior can be seen as state changes and how story can introduce a lot of exceptions and unique interactions. However, I still think there might be some ways to use OOP or other paradigms to handle these challenges and improve your code quality.

One possible way to deal with the exceptions and unique interactions is to use some kind of strategy or command pattern to define different algorithms or actions that can be executed by different characters or behaviors. This way, you can encapsulate the logic and data of each algorithm or action in a separate object or function, and then pass them as parameters or arguments to the character or behavior that needs them. For example, you could have a strategy or command object that defines the knife behavior for the nemesis character, and then pass it to the bathing behavior object that executes it when appropriate. This way, you can avoid using if-else statements to check for the character identity and instead use polymorphism or delegation to invoke the appropriate algorithm or action.

Another possible way to deal with the exceptions and unique interactions is to use some kind of decorator or adapter pattern to modify or extend the behavior of existing objects or functions without changing their interface. This way, you can wrap the original object or function with another object or function that adds some additional logic or data before or after executing it. For example, you could have a decorator or adapter object that modifies the bathing behavior for the rich girl character by adding some rage and flatter logic before and after it. Then, you can pass this object to the character object that executes it as if it was the original bathing behavior. This way, you can avoid using if-else statements to check for the character identity and instead use composition or inheritance to modify or extend the behavior.

I hope these suggestions are helpful for you. If you want more examples or explanations, please let me know.

Quote
Sure, I can try to show you an example pseudo code that would work within the example of the bathing scene. I’ll use the strategy pattern for defining different algorithms or actions, and the decorator pattern for
modifying or extending existing behaviors. Please note that this is just a rough and simplified example, and it might not be the best or the only way to implement these patterns.
Code:
// Define a base class for bathing behavior
class BathingBehavior {

// Define a constructor that takes a character object as a parameter
constructor(character) {
// Store the character object as an attribute
this.character = character;
}

// Define a method that executes the bathing behavior
execute() {
// Call the methods for each step of the bathing behavior
this.goToBath();
this.goToLocker();
this.changeToSwimsuit();
this.takeBath();
this.changeToNormalClothes();
this.exitBath();
}

// Define a method that makes the character go to the bath
goToBath() {
// Move the character to the bath location
this.character.moveTo(bathLocation);
}

// Define a method that makes the character go to the locker
goToLocker() {
// Move the character to their locker
this.character.moveTo(this.character.locker);
}

// Define a method that makes the character change to swimsuit
changeToSwimsuit() {
// Change the character’s clothes to swimsuit
this.character.changeClothes(swimsuit);
}

// Define a method that makes the character take a bath
takeBath() {
// Make the character enter the bath and stay there for some time
this.character.enter(bath);
this.character.wait(bathTime);
}

// Define a method that makes the character change to normal clothes
changeToNormalClothes() {
// Make the character exit the bath and change their clothes to normal
this.character.exit(bath);
this.character.changeClothes(normalClothes);
}

// Define a method that makes the character exit the bath
exitBath() {
// Move the character out of the bath location
this.character.moveTo(outsideLocation);
}
}

//---------------------------------------------------------------

// Define a subclass for knife behavior using strategy pattern
class KnifeBehavior extends BathingBehavior {
// Define a constructor that takes a character object and a knife object as parameters
constructor(character, knife) {
// Call the constructor of the base class with the character object
super(character);
// Store the knife object as an attribute
this.knife = knife;
}

// Override the method that makes the character go to the locker
goToLocker() {
// Call the method of the base class
super.goToLocker();
// Tap a bell inside the locker and pick up the knife
this.character.tapBell(this.character.locker);
this.character.pickUp(this.knife);
}

// Override the method that makes the character take a bath
takeBath() {
// Make the character enter the bath and submerge their body to hide the knife
this.character.enter(bath);
this.character.submerge();
}

// Override the method that makes the character change to normal clothes
changeToNormalClothes() {
// Make the character exit the bath and remove the tapped bell from locker
this.character.exit(bath);
this.character.removeBell(this.character.locker);
}

// Override the method that makes the character exit the bath
exitBath() {
// Remove the tapped bell from door and scan the environment before exiting
this.character.removeBell(door);
this.character.scan();
super.exitBath();
}
}

//---------------------------------------------------------------

// Define a subclass for rage behavior using decorator pattern
class RageBehavior extends BathingBehavior {
// Define a constructor that takes another bathing behavior object as a parameter
constructor(bathingBehavior) {
// Call the constructor of the base class with the character object of the bathing behavior object
super(bathingBehavior.character);
// Store the bathing behavior object as an attribute
this.bathingBehavior = bathingBehavior;
}

// Override the method that executes the bathing behavior
execute() {

// Call some additional logic before executing the bathing behavior object’s method
// Rage for some time about their clothes and change mood to angry
this.character.rage(clothes);

this.character.changeMood(angry);

// Call the bathing behavior object’s method
this.bathingBehavior.execute();

// Call some additional logic after executing the bathing behavior object’s method
// Look at mirror to fl

Quote
I see. You want to know how to call the super function in JavaScript and C#. Based on some web searches, I can explain how to do that.

In JavaScript, the super keyword can be used to call the methods of the parent class from a subclass. You can use it in two ways: as a function call (super(…args)) or as a property lookup (super.prop or super[expr]). For example, you can use super.goToLocker() to call the goToLocker method of the base class from a subclass. You can also use super() to call the constructor of the parent class from a subclass. For more details, you can check out this web source1.

In C#, the base keyword can be used to call the methods of the base class from a derived class. You can use it as a qualifier (base.method or base.field) or as a constructor call (base(…args)). For example, you can use base.goToLocker() to call the goToLocker method of the base class from a derived class. You can also use base() to call the constructor of the base class from a derived class. For more details, you can check out this web source.

I hope this explanation is helpful for you. If you want more examples or explanations, please let me know.
« Last Edit: April 19, 2023, 04:33:50 PM by gimymblert » Logged

Action Tad
Level 0
**



View Profile WWW
« Reply #15 on: April 27, 2023, 03:19:49 PM »

Yes, Classes are what you need. And in Unity you can assign a class, or any piece of code, to a specific graphic itself.

For example if one character holds something different than the others, you can associate code on whatever that graphic object is.

You could have base classes with methods that do general things,
goToBath, goToLocker for example being something each character can do.
But what they change to is different, and in Unity you can put that code on the locker image itself, so for example
the locker would have code on it that based on what character is taking something out,
it is different clothes based on that, but the whole code
for that can be applied to the clothes bin/locker image, and therefore not have to change.
And so you can organize the code via image/sprite objects in Unity, instead of in one long code file.


Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic