Atavism Blog

Atavism Blog

Here you will find Blog posts about atavism

  • Home
    Home This is where you can find all the blog posts throughout the site.
  • Categories
    Categories Displays a list of categories from this blog.
  • Tags
    Tags Displays a list of tags that have been used in the blog.
  • Bloggers
    Bloggers Search for your favorite blogger from this site.
  • Team Blogs
    Team Blogs Find your favorite team blogs here.
  • Login
    Login Login form

As developers look to get more serious about their game development we thought it would be a good idea to run another round of tests to get an idea of how well the Atavism Server performs when put under different loads. These numbers don’t just show how well the Atavism Server runs, but helps give you an idea of how much you can put in your game and how many resources your servers will need.

This first round of tests covers the basics of an empty server, static mobs, moving mobs and small groups of players. The information from the tests is given below with some analysis and recommendations at the bottom.

Notes:

  • CPU Usage (Ghz), Memory (GB) and Bandwidth In/Out (KBps) are recorded for each test
  • The world used was a 4km by 4km square. Mobs were randomly spawned within that area.
  • NavMesh is not used for these tests. Later tests will include this.

 

Test 1: Empty Server over 3 weeks

Time Lapsed

CPU Usage

Memory

Bandwidth In

Bandwidth Out

Startup

0.054

1.04

0

0

1 week

0.056

1.56

0

0

2 weeks

0.052

1.92

0

0

3 weeks

0.054

2.44

0

0

 

Analysis:

The basic Atavism Server looks to be very stable now, being able to run for long periods of time without issue. You will notice the Memory use does increase by a very small amount over time but the amount is minimal and it is good practice to run maintenance weekly which will reset it.

 

Test 2: Static Mobs, 1 template

a)      100 Mobs

Time Lapsed

CPU Usage

Memory

Bandwidth In

Bandwidth Out

Startup

0.053

1.08

0

0

30 minutes

0.053

1.08

0

0

 

b)      500 mobs

Time Lapsed

CPU Usage

Memory

Bandwidth In

Bandwidth Out

Startup

0.054

1.09

0

0

30 minutes

0.054

1.09

0

0

 

c)       1000 mobs

Time Lapsed

CPU Usage

Memory

Bandwidth In

Bandwidth Out

Startup

0.076

1.98

0

0

30 minutes

0.076

2.03

0

0

 

d)      5000 mobs

Time Lapsed

CPU Usage

Memory

Bandwidth In

Bandwidth Out

Startup

0.088

2.98

0

0

30 minutes

0.088

2.98

0

0

 

e)      20000 mobs

Time Lapsed

CPU Usage

Memory

Bandwidth In

Bandwidth Out

Startup

0.208

4.26

0

0

30 minutes

0.208

4.26

0

0

 

                Analysis:

As expected, static mobs have minimal effect on the server. Using the numbers above we see each mob using about 0.2 – 0.3MB of memory each. There appeared to be no change over time for the static mobs.

 

Test 3: Roaming Mobs (10 metre radius), 1 template

a)      100 Mobs

Time Lapsed

CPU Usage

Memory

Bandwidth In

Bandwidth Out

Startup

0.306

1.99

0

0

30 minutes

0.309

2.00

0

0

 

b)      500 mobs

Time Lapsed

CPU Usage

Memory

Bandwidth In

Bandwidth Out

Startup

1.02

2.26

0

0

30 minutes

1.15

2.26

0

0

 

c)       1000 mobs

Time Lapsed

CPU Usage

Memory

Bandwidth In

Bandwidth Out

Startup

2.44

3.04

0

0

30 minutes

2.46

3.05

0

0

 

d)      5000 mobs

Time Lapsed

CPU Usage

Memory

Bandwidth In

Bandwidth Out

Startup

5.01

7.89

0

0

30 minutes

5.18

7.97

0

0

 

e)      20000 mobs

Time Lapsed

CPU Usage

Memory

Bandwidth In

Bandwidth Out

Startup

20+

10+

0

0

30 minutes

20+

10+

0

0

 

                Analysis:

The server starts doing a lot more work when mobs are moving, which is expected. The server hit its limits on the 20000 mobs that were moving which will be due to the Quad Tree system being overworked having to constantly move the mobs between nodes as they are too close together having 20000 in a 4km by 4km area.

 

Test 4: Players in game with non-roaming mobs chasing them after being aggravated.

a)      100 mobs, 5 players, 10 mobs chasing

Time Lapsed

CPU Usage

Memory

Bandwidth In

Bandwidth Out

5 minutes

1.90

1.90

8

18

30 minutes

2.07

2.07

13

28

 

b)      500 mobs, 10 players, 50 mobs chasing

Time Lapsed

CPU Usage

Memory

Bandwidth In

Bandwidth Out

5 minutes

3.28

3.70

17

36

30 minutes

4.77

5.01

17

36

 

c)       1000 mobs, 20 players, 100 mobs

Time Lapsed

CPU Usage

Memory

Bandwidth In

Bandwidth Out

5 minutes

9.01

6.99

23

70

30 minutes

9.97

7.24

24

81

 

d)      5000 mobs, 25 players, 1000 mobs chasing

Time Lapsed

CPU Usage

Memory

Bandwidth In

Bandwidth Out

5 minutes

15.26

10.99

21

100

30 minutes

20+

11.03

26

122

 

                Analysis:

There’s good news and bad news here. The good news is that the bandwidth seems very much within reasonable levels. The bad news is causing mobs to aggro and chase players around seems to have a bit more of a hit on the server. The additional good news though is with this information the aggro system can be looked at and improvements made to bring it back into line (before the next release).

 

Conclusion:

Overall it’s very good news for the Atavism Server’s performance. From the tests so far there is one area that needs to be looked at and it is an area that is easily changed without effecting much else of the code. We will always be looking to improve these numbers as well, and there are a few plans in place to add additional optimisations to improve performance of mobs on the server.

With this information you need to think about your game and how many mobs and players you want to have and how powerful your servers will need to be. The one thing to keep in mind is to be smart about your mob placement and how many you have in your world.

Keep an eye out for more information about tests including more player performance testing and resource nodes.

 

 

 

 

                                                                                                                

Hits: 915

Posted by on in Uncategorized
AGIS Combat Traces

Most up-to-date version: AGIS Combat Traces (Google Drive)

Contents: AutoAttackTrace AbilityTrace EffectTrace CoordEffectTrace StatTrace EquipTrace

Hello everyone!

I'm fairly new to Atavism and, like many of you, am developing an MMO starting from scratch. This document started as my attempt to understand how auto-attack works but has evolved somewhat into a decent reference for the combat system. It is not complete, but it covers the basics. I define a trace as a logical chain of events in the code from start to finish. The traces involve files along the chain from player to server to database so they may refer to Java or CSharp files and include Unity, Server, and AGIS code references (AGIS requires a Veteran License). Keep in mind that many of the function references are not written verbatim from the code and are sometimes altered to make it easier to remember what certain variables represent. For example, an ability’s caster is referred to as ‘caster’, ‘obj’, ‘attacker’, and ‘source’ in different areas of the code and I simply wrote ‘attacker’ every time to keep it easy to remember. Sometimes I’ve directly copied comments from the code when they are descriptive. Furthermore, the language is often short-hand and not in complete sentences because I was moving fast. I encourage other developers to expand upon this and other plugin systems so that we can have basic references for everything.

Credits: to the Atavism Online team (Jacques and Andrew) for building the system and providing me permission to post this.

Version: Atavism 2.1.1 (will update if there are major changes, see Google Drive link at the top for most up-to-date version)

Note: This document is too long to fit into a blog entry so you'll only find part of it here and the rest on Google Drive.

AutoAttack Trace

This trace aims to document what happens from the moment a player clicks on a mob to the moment that mob’s hp is decremented. The journey through the code is a long one, but it is due to the level of sophistication of the intervening code that provides many contingencies for failure, result possibilities, and innumerable opportunities for customization.

Cursor.cs

  • Mouseover object click on ‘attackable’ object

  • NetworkAPI sendAttackMessage with object ID (Oid), attacktype (strike), attackstatus (true)

  • Message client “sent strike attack for” Oid (number displayed is not damage amount, it is object id

NetworkAPI.cs

  • SendAttackMesssage (Oid, attackType, attackStatus)

  • AutoAttackMessage sent via Client.Instance.NetworkHelper.SendMessage

CombatPlugin.java

  • AutoAttackHook intercepts msg

  • Obtain attacker id and target id and attackstatus

  • Load CombatInfo for both

  • Lock player and target CombatInfo objects and only unlock when attack is over

  • if status is false or obj dead or no target or target dead, stopAutoAttack() and set COMBAT_TAG_OWNER to next aggressive mob, else obj.setAutoAttack(targetOid)

CombatInfo.java

  • obj.setAutoAttack(targetOid), lock first and unlock when done

  • if newtarget is same as oldtarget, return (we are already attacking)

  • if not, we need to start a new attack, so setCombatState (true) and if not scheduled, schedule the auto attack with schedule(getAttackDelay() which checks attack_speed) as the update interval for CombatInfo object run() method

  • remove the old attacker from our list and if we have a null new target, we want to turn off combat by setCombatState(false), but if we do have a new attacker we want to addAttacker(target, getOwnerOid()) to the list

  • setCombatState sets COMBAT_PROP_COMBATSTATE, and broadcasts message that we are in combat and that weapons are unsheathed.

CombatPlugin.java

  • addAttacker (OID target, OID attacker)

  • lock combatplugin to do this

  • Add player’s OID as one of the attackers to this mob’s attacker hash map or create a new attacker map if it is null

  • make sure this target is tagOwner (current target?)

CombatInfo.java

  • run() method for both attacker and target is scheduled according to their attack_speed (getAttackDelay())

  • Lock the CombatInfo object for attacker and target

  • Get the attack ability id from COMBAT_PROP_AUTOATTACK_ABILITY and continue if successful

  • if target is null or the entity it points to is null, stop scheduling

  • Otherwise CombatPlugin.resolveAutoAttack(this CombatInfo) for both attacker and target and schedule next run() method with getAttackDelay()

CombatPlugin.java

  • resolveAutoAttack(this CombatInfo)

  • Get CombatInfo objects from attacker and target

  • if target is null or attack ability not valid, return

  • Some temp AI code here but the running code does the following: if the attacker isn’t the player (isUser(), COMBAT_PROP_USERFLAG false) then set the maxHealth, curHealth to health-max and health stats and healthPercent calculated as float, but these numbers aren’t used for anything locally currently.

  • Get AgisAbility object ability from abilityID

  • More temp AI code here: If the abilityID is no longer the autoattack (currently not possible), the previous AI code wants the mob to cast a spell, so a message is sent to the client that the mob begins casting the ability name.

  • Duel code: if both attacker and target are players get duelIDs for both and if they are in the same duel, then setDuelID on the autoattack ability (which does nothing as of now ?)

  • AgisAbility.startAbility(ability, info, target, null) finally executes the autoattack for attacker and target (as earlier, delay is based on attack_speed) --> See AbilityTrace

 

Ability Trace

Abilities can range from auto attack to heals to sophisticated high level damage spells and may even be general enough to be completely unrelated to combat if creativity is used.

AgisAbility.java

  • startAbility(AgisAbility ability, CombatInfo source, CombatInfo target, AgisItem item, Point loc)

  • For this ability, generateState(source, target, item, loc) which returns AgisAbilityState obj (see AgisAbilityState constructor for details--if the ability is TargetType.SELF it sets the target to the source=attacker and a message hook is setup in case the attacker is moving so the ability can be interrupted) and updateState() called

AgisAbilityState.java

  • updateState ()

  • source (attacker) and target are already defined from the generateState earlier, so this first locks the attacker and the target and all potentialTargets which may change throughout the course of the ability

  • ActivationState state is INIT by default, enum defined in AgisAbility. Ability can be in INIT, ACTIVATING, CHANNELING, ACTIVATED, COMPLETED, CANCELLED, INTERRUPTED, or FAILED states

  • Depending on state, switch will perform the correct type of update

  • For all updates, a series of checks is performed on the ability to determine whether to go forward. if the checks succeed, AbilityResult.SUCCESS is returned and if they fail, a different AbilityResult is returned and AgisAbility.interruptAbility is called with the result, which resets the states (if fails during init runs init coordinated effects of ability? --> See CoordEffectTrace ) and sends a message.

AgisAbility.java

  • checkAbility(source, target, state)

  • The checks for INIT are if the attacker is ready (not performing another action), if the target type makes sense depending on the ability target type, if the target species makes sense depending on the ability type, if the ability only works on specifically named targets and that target is selected (allows object interaction ?), if the player/target is alive, or if the target should be dead (resurrection), if the target is within range for starting the ability (too close or too far), and finally if the ability has any effect requirements that are active like just having dealt a crit, having last attack dodged, having used a certain ability within the past 5 seconds, etc (works by storing all effects player gets into a list and then listing what numeric effect values an ability requires to activate--can be >1effect required).

  • The checks for other states (including INIT) are if the attacker has/knows the ability, if the ability is a passive ability that shouldn’t be activated, if the attacker requires a tool or reagent in their inventory and if they have it, if the attacker has enough ‘costProp’ (the property required by the ability, usually mana) or vigor (which is like rage in that some abilities add vigor and some need vigor) to use the ability, if the target is within range for continuing the ability (too close or too far), and finally if they are in the correct combat stance (not fully coded).

(CombatMeleeAbility.java)

  • For CombatMeleeAbility, which is the default player auto attack ability, it extends AgisAbility, so retains the same properties as AgisAbility and adds the following: checkAbility does the default checks and then sendAbilityFailMessage with result if failure. Otherwise, checkPosition (not coded yet), then checkEquip to see which weapon type is required and if it is equipped.

  • If ACTIVATING, reset params to default, call changeCoordinatedEffect(“success”) to reset the result parameters (which may become block/partial resist later).

  • The next piece of code is confusing in regards to player vs target, especially the in-line comments. Checks target’s defenses: sets defensiveType to ‘dodged’ (default) and weapType to target’s weaponType (possible error--shouldn’t this be the weapon of the attacker?). Checks if target has a parry effect for this weapon type and if so, does the target also have the skillType required. If so, set defensiveType to ‘parried’.

  • hitChance = CombatHelper.CalcPhysicalHitChance(attacker, target, skillType)

CombatHelper.java

  • CalcPhysicalHitChance(attacker, target, skillType)

  • dexterity taken from CombatPlugin.PHYSICAL_ACCURACY_STAT, targetPerception = 20 (arbitrary, temp?), targetLevel taken, attacker skillLevel set to attacker level * 10 if attacker doesn’t have skill (arbitrary?), and to attacker’s skill if he has it.

  • hitChance = Math.atan((dexterity * skillLevel) - ((targetPerception * targetLevel)/1400) * 0.3) + 0.7

  • This calculation is arbitrary and there is a ‘New Formula’ above it commented out.

(CombatMeleeAbility.java)

  • Next get a random value [0,1] and hitRoll = rand * 100

  • If the rand > 0.95, the ability has missed. Set attackerResult in params to missed = 3, changeCoordinatedEffect(“missed”), get casterResultEffect and targetResultEffect for the miss, which are effects that are later applied to both parties in the event of the miss.

  • If the rand > hitChance, the ability was dodged or parried if the target defensiveType was set to parried earlier. Set attackerResult in params to dodged = 5, changeCoordinatedEffect(“dodged”), get caster/target result effects, similar for parry.

  • There are some other commented-out possibilities including blockChance, critChance

  • Else, the ability has hit successfully and attackerResult = 1. Even if the ability didn’t hit, AbilityResult.SUCCESS is set to continue updating the ability state.

AgisAbilityState.java

  • In updateState(), If AbilityResult.SUCCESS

  • If INIT, setCurrentAction for attacker to AgisAbilityState.INIT, then move to nextState(), run coordinated effects for this state using effect.invoke(getSourceOid(), getTargetOid(), location, this) -->See CoordEffectTrace and then another switch statement controls the new state

  • If ACTIVATING, which is next state, check the activation time and if there is an animation for it, run that (and also check the castingAffinity and send that to the attacker as well ?), then send the casting started message, set the duration of the activating state (getActivationTime()), and then schedule the run() function of AgisAbilityState to execute once that time has elapsed, which simply calls updateState() again to check the state and advance it. This then calls ability.completeActivation.

AgisAbility.java

  • completeActivation(state)

  • Gets attacker, target CombatInfo objs

  • Checks if reagents are required and if consumeReagents is active before removing reagents from inventory via AgisInventoryClient.removeGenericItem(OID, itemID, removeStack?, numToRemove)

  • Checks for a costProp (like mana) and subtracts from that property using CombatInfo.statModifyBaseValue(costProp, -activationCost), does similar with vigor stat except adds or removes (vigor is like building rage with abilities), then sendStatusUpdate() which is not coded.

  • Checks for effects that must be consumed by attacker and target (required effects or debuffs) and removes them with AgisEffect.removeEffect(EffectState existingState).

  • If attacker is player, send message to client that ability was used

  • Activate cooldowns in CooldownMap with Cooldown.activateCooldowns(cooldown collection, CombatInfo obj, quickness value = 100)

  • Smoo only? decrementWeaponUses message broadcasted ?

(CombatMeleeAbility.java)

  • For CombatMeleeAbility, it retains the same properties from completeActivation and adds the following: also setCombatState(true) on the target, (if AREA_ENEMY =AoE ability gets a list of attackable targets in radius with getAoETargets(attacker’s CombatInfo)).

  • For every activation effect, put attackerResult, skillType, hitRoll into params. Get the target of this effect, and next condition checks all AoE targets and finally calls AgisEffect.applyEffect(activationEffect(i), attacker, target, params), (-->See EffectTrace) to all targets. If not AoE, checks if the target is attacker (self) and then applies the effect with the function above before clearing the params.

  • Last, the casterResultEffect and targetResultEffect are applied (-->See EffectTrace) in the same way.

AgisAbilityState.java

  • Next it moves to nextState() which is CHANNELLING or ACTIVATED or (COMPLETED if not persistent) depending on ability and coordinated effects are run for that state -->See CoordEffectTrace.

  • If CHANNELLING, duration of the CHANNELLING state is set to the number of pulses * pulse time and it is scheduled to run again after one pulse time. ability.pulseChannelling(this) is called (which subtracts from costProp the cost per channel pulse) and nextPulse is incremented and scheduled to run after pulse time and updateState() returns so it can repeat until the number of pulses is up before moving to nextState() which is ACTIVATED or COMPLETED depending on ability persistence. For CombatMeleeAbility, AgisEffect.applyEffect(channelEffect, attacker, target) is called, -->See EffectTrace

  • If ACTIVATED, the current action is set to null since the attacker is no longer busy activating/channeling, the ability is added to active abilities via addActiveAbility(this) and update is scheduled for ActivePulseTime.

  • After return pulseActivated(this) is called (which subtracts from costProp the cost for an activePulse) and nextPulse incremented. Then the function schedules for ActivePulseTime and returns indefinitely without progressing to the next state (is this return intentional?). The only way for it to move to completed is for the state to be changed to COMPLETED elsewhere. For CombatMeleeAbility, AgisEffect.applyEffect(activeEffect, attacker, target) is called, -->See EffectTrace

  • If COMPLETED, setCurrentAction to null since we are no longer busy, terminate the casting animation and castingAffinity (?)

  • Finally unlockAll because the ability is finished

...Continued at AGIS Combat Traces (Google Drive)

 

Hits: 5209

Posted by on in Uncategorized
Dialogues

To create a more lively and interactive world you want NPC’s that have something to say to players when they click on them. This can range from a friendly chat message such as “Hi, welcome to the town, it is always nice to meet new travellers” to story based dialogues that can have response options.

Atavism provides support for these features through the Dialogue System. Developers can create single or chained dialogues using the Atavism Editor and assign these to NPCs with the in-game spawn tool. The steps to create Dialogues are detailed below along with possible ideas/plans for the future.

 

Creating a Simple Chat Dialogue

Simple Chat Dialogues are used for a simple greeting that does not present any response options. Chat Dialogues can be used in conjunction with other NPC Interaction options such as Merchant and Quest Giver. When the player clicks on the NPC it will display the chat dialogue at the top with any other interaction options listed below.

To create the Dialogue click on the Dialogue Plugin in the Atavism Editor. Give the dialogue a name (used to help find it later) and leave the Opening Dialogue and Repeatable boxes ticked (opening dialogue means it comes up as soon as you talk to the NPC, repeatable means it can show up numerous times).

Change the prereq settings as desired. If any are set, the dialogue will only come up if the player matches the requirement. A common setting will be to set a faction and stance so the NPC will only talk to players who it is friendly with.

The final step is to write in the dialogue text. Click save and your dialogue is ready to be added to an NPC.

 

Creating a Chained Dialogue with Options

Chained dialogues allow multiple blocks of text to be shown to a player using buttons to progress to the next dialogue. Each dialogue can have up to three actions or choices which can cause the next dialogue to show up (based on their choice).

Note: If an NPC is given a dialogue with actions/choices along with other interaction options (such as quests) the dialogue will not come up straight away, but the title of the dialogue will be shown as a button the player can click on.

Creating the chained dialogue starts off the same as a standard dialogue (instructions above). Once the dialogue has been saved it will appear with some Actions at the bottom. Adding the next dialogue in the chain is achieved by filling in the text for the action (the response the player can choose) and clicking on Create Dialogue.

You will be back to the Create a New Dialogue window, but with two differences: there is a Go Back to Previous Dialogue button, and Opening Dialogue has been unchecked. When you save this new dialogue, the Atavism Editor will automatically link this new dialogue up to the previous one.

With the second dialogue saved, the Go back button can be clicked to return to the first dialogue and more actions can be added by clicking Add Action. Further chained dialogues can also be added to the second dialogue by repeating the same process from the second dialogue, allowing the creation of the complex tree of chained dialogues if wanted.

 

Adding a Dialogue to an NPC

To add a Dialogue (or multiple) to an NPC, log in game with an admin account and bring up the spawner tool (type /spawner in the chat box). Either bring up the UI to spawn a new mob, or select and existing mob and click on the Dialogues button in the UI. From here you can select which dialogues to give the spawn.

Once happy with your selection, close the Add Dialogues window and click Spawn/Save. Your NPC will instantly offer the dialogue to players who click on it.

Note: For chained dialogues, you only need to add the first dialogue in the chain (the one with Opening Dialogue ticked).

 

Final Notes

You may notice that the Action Type drop down has two more options: Quest and Ability. These aren’t supported yet, but the plan is to allow the starting of a Quest or the performing of an ability at the end of a dialogue chain. Possible ideas for the ability use would be teleporting the player somewhere or giving them a buff when they click on the option provided by the NPC. A lot of potential to come. This will open up a lot of potential for NPC interaction.

 

Hits: 2515
0
Creating and Accessing Properties Part II

In Part I we covered creating and adding properties to our characters and mobs. It’s all good having properties on them, but to be of any use we need to access them. This second part covers accessing properties both in-game and at the Character Selection scene along with setting up listeners for when properties change.

 

Accessing/Reading Character Properties at the Selection Scene

Properties can be easily accessed for any character in the selection screen by adding the property key (name) to the end of the CharacterEntry object, such as:

string gender = (string)characterSelected ["gender"];

Where characterSelected is the CharacterEntry object for the currently selected object. The property value will need to be converted to the variable type wanted (such as string or int). Be careful when adding new property getting code here as if the character doesn’t have the property it will error out the Character Selection scene.

Note: The example above or similar can be found in the LoginUI.cs or CharacterSelectionCreation.cs (UMA) files, generally in the DrawCharacterSelectUI() or equivalent.

 

Accessing/Reading Character or Mob Properties in game

Getting player and mob properties in game is done by getting the ObjectNode or AtavismNode for the player/mob and calling GetProperty(“property_name”) on it. For example, in the MobController3D class:

bool dead = (bool)GetComponent<AtavismNode>().GetProperty("deadstate");

The slightly tricky part is getting the ObjectNode/AtavismNode, as depending on the situation there are different ways.

Getting the ObjectNode for the local player is done using:

ClientAPI.GetPlayerObject()  - See PlayerPortrait.cs

 Getting the ObjectNode for the current target for the local player is done using:

ClientAPI.GetTargetObject() - See TargetPortrait.cs

 To get the ObjectNode for any other mob or player you can get it by using:

ClientAPI.GetObjectNode(long oid) – if you have the OID for it

 Or you can get the AtavismNode if you have the gameObject to work with:

gameObject.GetComponent<AtavismNode> () - see Cursor.cs Update() function.

 

It may be a bit confusing at first (especially if you are not a programmer), but if you look through the files mentioned above it should give you a better idea.

 

Creating Property Change Handlers

Property Change Handlers are used to run updates/changes when a property has been changed (such as the health of the player has gone down and the health bar needs to change to reflect that). A listener generally wants set up in either the Start() function of a class, or in the ObjectNodeReady() function when the script is going to be attached to a mob (such as the MobController3D).

Similar to the getting of properties, the registering of a change handler requires either the ObjectNode or AtavismNode and is done in the following format:

ClientAPI.GetPlayerObject().RegisterPropertyChangeHandler(“health”, HealthHandler);

The first parameter is the property to listen for, and the second is the name of the function to run when the stat has changed. The function must match this setup:

public void FunctionName(object sender, PropertyChangeEventArgs args) {
  // Do whatever you want in here
}

 

For the health example for above the HealthHandler function looks like:

public void HealthHandler(object sender, PropertyChangeEventArgs args) {
    health = (int)ClientAPI.GetPlayerObject().GetProperty(“health”);
}

All these few lines of code do (combined with the RegisterPropertyChangeHandler above) is listen for when the health property changes, then set the local health (for the health bar) to whatever the value is the player has now.

 

Title Property Tutorial

Ok so we have now covered how to set a property and get a property, it's now time to try put in a new example in full: a Title property that is displayed before a player’s name (such as a Mr, Mrs, Dr, Warlord, Grand Marshall).

Note: this tutorial is designed for the basic demo project. If you are using UMA or NGUI with your project you may need to edit some other files instead.

Step 1) Editing the Character Creation Code to add the property

Open up the LoginUI.cs file and near the top just under:

List<CharacterEntry> characterEntries;

add a new list for the potential Titles and a string for the chosen title:

public List<string> titles;
string chosenTitle = “”;

 We will then add some code in DrawCharacterCreateUI () to draw buttons for each title option. I have put this just before line 239 - GUILayout.Label("Name:");

GUILayout.Label("Chosen Title: " + chosenTitle);
foreach (string title in titles) {
            if (GUILayout.Button(title)) {
                        chosenTitle = title;
            }
}

 And just below: properties.Add("gender", gender); (at line 255) add:

properties.Add("title", chosenTitle);

 

Step 2) Adding a list of titles to your game.

Load up the Login scene and click on the Main Camera object in the scene. You should now see a new Titles property listed in the Login UI component on it. Here you can specify which titles you would like players to choose. I have attached an image showing how it should look (along with my selection of titles).

b2ap3_thumbnail_TitlesList.PNG

 

Step 3) First Test

With a selection of titles added, you should now play your game and try create a new character. You should see a list of buttons that match your titles and you can click one to set it for your character.

 

Step 4) Altering the display of character names

Now that we can give our characters a title property it's time to get that property in game. For this tutorial I'm going to modify the MobController3D.cs file which draws the names above the heads of characters/mobs.

Open up the MobController3D.cs file and add the following line somewhere in the UI Fields region (lines 111-119 - #region UI Fields):

string title = "";

This is to store the value of the title. Next we will update the OnGUI() function to display the title before the name. Look for these lines:

if (!isPlayer)
            GUI.Label (rect, gameObject.name);

 And modify the second line so it says:

GUI.Label (rect, title + " " + gameObject.name);

You may want to comment out the first line so you can see the title above your character as it will make testing much easier.

Note: Commenting is done by adding // before the line of code.

I also found out that the line up about 5 lines above that says:

Vector2 size = GUI.skin.GetStyle (styleName).CalcSize (new GUIContent (gameObject.name));

needs to be changed to:

Vector2 size = GUI.skin.GetStyle (styleName).CalcSize (new GUIContent (title + " " + gameObject.name));

otherwise it doesn't have enough space to draw the title and name.

 

Step 5) Creating the Property Change Handler

The last step before final testing is to add a property change handler for the title property.

I created a new function called HandleTitle which looks like:

public void HandleTitle (object sender, PropertyChangeEventArgs args)
{
           title = (string)Client.Instance.WorldManager.GetObjectNode(oid).GetProperty("title");
}

 

It can go anywhere in the file, I added it after HandleCombatState().

The final line of code is to set up the registration of the property change handler. Find your way to the ObjectNodeReady() function and just after the line:

GetComponent<AtavismNode>().RegisterObjectPropertyChangeHandler("combatstate", HandleCombatState);

add:

GetComponent<AtavismNode>().RegisterObjectPropertyChangeHandler("title", HandleTitle);

 

Note: the line before may look slightly different, it is changing in update 2.2.

 

Step 6) Final Test

Finally, log in with your new character that has a title and see what their name looks like! Here is how mine turned out:

 b2ap3_thumbnail_WarlordSoomy.PNG

 

Extension activity:

Try get the Title to show up with the player name in the Player Portrait by editing the PlayerPortrait.cs.

 

Hits: 2949
0
Creating and Accessing Properties Part I

Every player character and mob in Atavism uses properties to keep track of appearance, combat and other game progression information. Properties are generally given to the player/mob on creation and accessed and updated as events happen in the game.

Properties can only be created and updated by the server (to prevent synchronisation issues and cheating), and are sent down to the client as the player comes within range of another player or mob.  The client (Unity) can listen for property changes and/or grab a property value from a player/mob and use it to display information in various ways (such as numbers/text in the user interface or the visual appearance of the player).

In Part I of this two part blog how to create properties for your players and mobs is explained below. Part II next week will cover how to access that data in Unity and set up listeners. Part II will also be accompanied by a tutorial to add a Title property to your characters which will be displayed beside their name.

 

Creating Properties for Players

Player properties can be added in different ways depending on the property type. Stat properties for combat (such as health, strength etc.) are added using the Stat plugin in the Atavism Editor (see more here: <insert page>). Other properties, such as appearance or general game progression properties, can be added either in the Character Creation scripts in Unity, or in the Character Template in the AGIS code on the server.

Adding Properties in Unity

Adding properties to the character creation in Unity is done by adding in a new entry to the properties map that is sent to the NetworkHelper.CreateCharacter() function. In the standard Atavism setup this is located in the LoginUI.cs file, in the DrawCharacterCreateUI() function, and in the UMA setup it is in the CharacterSelectionCreation.cs file, in the CreateCharacter() function.

Any new properties added in to via the Unity method need to have “custom:” at the start so the server knows not to confuse it with the standard properties. For example, an age property to be given to player characters upon creation:

properties.Add ("custom:age", 27);

Note: An example of a few properties are provided in the LoginUI file, commented out among the rest of the property setting code in the character creation.

Adding Properties in AGIS

Adding properties in the AGIS code is done by modifying the CharacterTemplate.java file found in the objects package. New properties need to be added to the template for a character which is setup in the createCharacter() function. Adding the properties in the AGIS code comes with the added complexity of deciding which Namespace the property should be added to (Namespaces relate to different areas of the server such as combat, inventory, world). Properties relating to combat should be added to the CombatClient.NAMESPACE, inventory related ones to InventoryClient.NAMESPACE, and most others to the WorldManagerClient.NAMESPACE. Other Namespaces exist, but I won’t go into detail here about what it all means as it is another topic in itself.

Note: All custom properties from Unity are added to the WorldManagerClient.NAMESPACE.

As an example, if I wanted to add the age property directly on the server rather than through Unity, I could add:

player.put(WorldManagerClient.NAMESPACE, "age", 27);

 

Creating Properties for Mobs

New Properties for mobs can only be done in the AGIS code, by editing the loadMobTemplates() function in the MobDatabase.java file located in the database package. Mobs use the same template system as the CharacterTemplate setup listed above. For example, mobs could be given an age property as well by adding:

tmpl.put(WorldManagerClient.NAMESPACE, "age ", 27);

to the loadMobTemplates function.

 

Final Notes

It is important to note that any new properties will only exist for newly created characters. You can always wipe all existing characters in your game by re-sourcing the install.sql file if needed.

All properties of your character, or of the player/mob you have targeted can be viewed by typing /props into the chat box when in game. It is a good idea to try this after creating a new property to make sure it is coming up as expected.

 

 

 

 

 

Tagged in: atavism MMO properties
Hits: 3412
0

Intel

Unity

S5 Box

Sign On