Day 18 - Who Do You Hate?

Peek into the AI for some battle insights.

More detailed violence

We can watch the NPCs take damage and eventually expire, but it’s not exactly super-clear where the damage is coming from. If we poke into the server code to see what’s going on:

DragonsSpine/GameObjects/GameLiving/NPC/AI/AI.cslink
2641
Command.ParseCommand(npc, "poke", npc.mostHated.Name);
DragonsSpine/GameObjects/GameLiving/NPC/AI/AI.cslink
2647
Command.ParseCommand(npc, "shoot", npc.mostHated.Name);
DragonsSpine/GameObjects/GameLiving/NPC/AI/AI.cslink
2662
Command.ParseCommand(npc, "kick", npc.mostHated.Name);
DragonsSpine/GameObjects/GameLiving/NPC/AI/AI.cslink
2666
Command.ParseCommand(npc, "kill", npc.mostHated.Name);
DragonsSpine/GameObjects/GameLiving/NPC/AI/AI.cslink
2674
Command.ParseCommand(npc, "jumpkick", npc.mostHated.Name + "/Ki Yah!/!");

Ok, I think we see a pattern. If we know the NPC’s mostHated, we can be pretty sure that’s who the NPC is attacking. Luckily we’ve already added it to the LiveNPC table in the database, so it’s just a matter of surfacing it.

Assets/DragonsSpine/DAL/DBNPC.cslink
127
128
129
130
npc.hasMostHated = !(dr.IsNull(dtNPCs.Columns["mostHatedId"]));
if (npc.hasMostHated) {
    npc.mostHatedId = Convert.ToInt32(dr["mostHatedId"]);
}
Assets/Managers/NpcManager.cslink
129
130
npcScripts[npc.worldNpcID].hasMostHated = npc.hasMostHated;
npcScripts[npc.worldNpcID].mostHatedId = npc.mostHatedId;

Then we make an angry red arrow to point at the object of most hatred: We need a way to find the position of our foe:

Assets/Managers/NpcManager.cslink
174
175
176
177
178
179
public Vector3 locateNpc(int npcId) {
    if (npcScripts.ContainsKey(npcId))
        return npcScripts[npcId].transform.position;
    else
        return Vector3.zero;
}

And now we can display our hatred:

Assets/NpcScript.cslink
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// display hatred
if (hasMostHated) {
    if (myHatred == null) {
        myHatred = Instantiate(hatredPrefab,
                               Vector3.zero,
                               Quaternion.identity) as Transform;
        myHatred.parent = this.transform;
        myHatred.localPosition = new Vector3(0f, 0f, 0.2f);
        myHatredRend = myHatred.GetComponent<Renderer>();
    }
    hatedLocation = npcManager.locateNpc(mostHatedId);

    Vector3 hatredDir = hatedLocation - transform.position;
    hatredDir.z = 0f;
    hatredDir = hatredDir.normalized;

    Quaternion qDir = new Quaternion();
    qDir.SetLookRotation(hatredDir, new Vector3(0f, 0f, 1f));
    myHatred.rotation = qDir;

    myHatred.localScale = new Vector3(0.3f, 1f, 1f);
    myHatredRend.enabled = toBeSeen;
} else {
    if (myHatred != null) {
        myHatredRend.enabled = false;
    }
}


Cohatred

That’s cool, but the Update method in NpcScript is getting crowded. Can’t we do this in a cool, coroutine-ish way?

Assets/NpcScript.cslink
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public bool HasMostHated {
    get { return hasMostHated; }
    set {
        if (hasMostHated != value) {
            hasMostHated = value;
            StopCoroutine("Hatred");
            StartCoroutine("Hatred");
        }
    }
}
public int MostHatedId {
    get { return mostHatedId; }
    set {
        if (mostHatedId != value) {
            mostHatedId = value;
            StopCoroutine("Hatred");
            StartCoroutine("Hatred");
        }
    }
}
Assets/NpcScript.cslink
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
IEnumerator Hatred () {
    while (hasMostHated && !presumedDead && hits > 0f) {
        if (myHatred == null) {
            myHatred = Instantiate(hatredPrefab,
                                   Vector3.zero,
                                   Quaternion.identity) as Transform;
            myHatred.parent = this.transform;
            myHatred.localPosition = new Vector3(0f, 0f, 0.2f);
            myHatredRend = myHatred.GetComponent<Renderer>();
        }
        hatedLocation = npcManager.locateNpc(mostHatedId);
        
        Vector3 hatredDir = hatedLocation - transform.position;
        hatredDir.z = 0f;
        hatredDir = hatredDir.normalized;
        
        Quaternion qDir = new Quaternion();
        qDir.SetLookRotation(hatredDir, new Vector3(0f, 0f, 1f));
        myHatred.rotation = qDir;
        
        myHatred.localScale = new Vector3(0.3f, 0.1f, 1f);
        Vector3 startPos = transform.position + hatredDir * transform.localScale.x * 0.5f;
        Vector3 endPos = hatedLocation - hatredDir * transform.localScale.x * 0.5f;
        startPos.z += 0.2f; endPos.z += 0.2f;
        myHatred.position = Vector3.Lerp(startPos, endPos, Time.time % 1f);
        myHatredRend.enabled = toBeSeen;

        yield return null;
    } 
    if (myHatred != null) {
        myHatredRend.enabled = false;
    }
}

And while we’re at it, we take advantage of the added efficiency to animate the arrows a bit.


Day 18 code - client