• Register

Antegods is an action-packed arena shooter in which the remnants of the Mayan civilization have taken to space. Two teams of 4 players each control customizable stonepunk mechs to hunt for energy and fight off opponents in a procedurally generated and highly destructible map. The ultimate goal is to activate enormous ‘titan’ statues that will bring explosive mayhem down upon the enemy team. Whoever wins these tactical battles climbs an intergalactic tournament ladder, ultimately becoming gods themselves.

Post news Report RSS Antegods Development Update 2: Totems ’n’ Titans

Antegods Development Update 2: Totems ’n’ Titans

Posted by on

We’re almost off for seasonal holiday festivities at Codeglue, but before we lock up our office, we’d like to share another Antegods development update with you. Just like the first time, our three leads will convey intimate details of what they’ve been working on, and this time we even have a bonus code update from our intern Aldo.

If you have any feedback on these development updates, please let us know. The posts serve as a way to publicly track our progress, but we want to make them fun and useful for you, too. So get in touch with your comments!

Art - Tom

Today I’d like to tell you about the rather sizable playable characters and enormous titans you’ll find in Antegods.

We call the playable characters ‘totems’. These are actually big statues that function as fighter jets. A native (which we discussed two weeks ago) can sit inside a totem’s head and control it. Each totem consists of 3 parts: the body/head, the wings and the weapon. Each of these has its own stats. As a player, you can pick all kinds of versions of each, and combine them to create your perfect, personalized battle machine.

In the current playable version of the game, the character is a simple capsule, while in an early prototype we had a fully animated character. This looked so epic, we decided to focus on these characters. For now we’ve limited ourselves to designing two: a ‘heavy’ and a ‘normal’ totem.

image


This is the normal totem. On the left you see a bit of the process, on the right the final designs.

image

In the next sprint we’re going to model and animate the characters, so we can see what they’ll look like in-game. But the prototype already shows us that it brings a lot of dynamism to the game’s look and feel.

On to the titans! These things are our unique selling point. Enormous dock-able battle fortresses that, like the totems, take the shape of statues. When your titan is destroyed, you lose the game. But because he functions as your base and has the most powerful weapon ever, you need to bring him to your front-line to destroy the other team’s titan.

To make this more interesting, we’ve added states to the titans. In the image below you can see what the states are and what functions a titan has.

image


When you’re playing Antegods and you find a fully activated titan flying towards you, it should scare the crap out of you. At the same time, when you’re the one flying your fully activated titan close to the losing team, you should feel super powerful, perhaps even invincible.

Design - Wytze

Last time I introduced the design of the overall game, and in this update I want to give you more insight into our design process.

I wanted to get a design headstart on my own before production started. That’s why I created the strategy prototype, a turn-based version of Antegods in which each team of 5 totems is controlled by a single player. This allowed me to test and iterate the mechanics and dynamics of Antegods for several months, while the actual Antegods wasn’t ready yet. There are obviously big differences between the turn-based prototype and the real-time game, but much of the acquired design knowledge still applies. To show how this works in practice, I’ll elaborate on the design evolution of ‘silk’ sources in the strategy prototype.

Two ways to collect silk

Silk is the main resource in the game. Gathering silk and powering up the titan with it structures the matches, as the titans’ movement speed increases and their weapons activate as more silk is collected. These characteristics can then be used to destroy the opposing titan.

There are two different sources of silk: plants and spindle points. Plants are a one-time silk pick-up; their role is to reward and promote exploration of the level. Players usually explore the level and pick up any plants they come across, rewarding the first player that finds it. The plant’s mechanics are really simple and effective, and throughout our testing their design hasn’t changed.

Spindle points

The role of spindle points is to provide silk throughout a match and create a point of conflict for the teams. Spindle points, in contrast to plants, have gone through many changes.

Initially they were classic control points: occupy the spindle point to start capturing it, and once it’s been captured, you gain silk. The other team has to occupy the point to make it neutral, and then capture it for themselves to gain Silk. In tests, however, the spindles were easily defended once captured, which promoted passive defensive play. But as we’re looking for explosive action in Antegods, that didn’t meet our goals.

To solve this, we gave each spindle point a finite amount of silk. Once a spindle point was depleted, it took several turns before it gained new silk and could be captured again. This meant teams gained a limited amount of silk before having to take action and capture the point again. In testing this was a step in the right direction, but players could still defend control points too easily.

Next, we made spindle points turn neutral automatically unless a player was holding the point. The intent was to force players to actively spend resources (time) in order to defend the spindle point and gain silk. In playtests we saw the desired effect: it was a feasible tactic for players to overtake a spindle point and gain silk from it.

Moments of conflict

Our next goal was to structurally create moments of conflict. Up to this moment we had 3-4 spindle points that were active simultaneously. Having fewer points should concentrate more players around the same point, which should lead to more battles.

To make this happen, we activated a limited number of spindle points simultaneously. When all spindles were depleted, a new round with new randomly activated spindle points started. This mechanic was a mild success. On the plus side, players couldn’t stick to the same point at all times, and the game flow was less predictable and rigid. However, the activation was too random and unpredictable, leading to luck-based situations out of the players’ control.

We then removed the activation of random points. Instead, all spindles now activate simultaneously after they’ve been depleted. We also simplified the spindle points, while keeping most characteristics. They now automatically spawn a ball of silk, and once that’s collected by either team, they start spinning another ball of silk, until they’re depleted. This rewards players who stay near a spindle point, but also makes it easy for attackers to steal the silk.

I’m very happy with the way the spindle points evolved, and by now the real Antegods game has progressed far enough to test these mechanics in real-time. As such we have retired the turn-based strategy prototype, which has been a great method of progressing our design without creating the full game.

Code - Niels

Last sprint we focused mainly on implementing the titan design. One of the main programming tasks was the docking feature, as a totem can dock into certain parts of the titan. This way he or she can either play as its pilot, or as its gunner. I’ll explain a bit more about how we use MVVM and Messaging by giving a more concrete example from the docking of the titan.

image


The titan itself has various docking targets, giving access to either the gunner or pilot position. The docking target is implemented as a model of the titan.

DockingTarget.cs

SphereCollider { get; set; }Layer { get; set; DockingPosition { get; set; }voidUpdate();

DockingPosition is an enum:

enum DockingPosition 
{ 
   Pilot 
   PilotExit 
   Gunner 
   GunnerExit 
}

The Update() method is responsible for detecting if a totem is in range. If that’s the case, it’ll send a message saying it can dock into either the Pilot or Gunner position. If a totem leaves, it’ll send a message saying it can’t dock anymore. These messages are called a DockingStatus message.

DockingStatus.cs

internal long totemNetworkId; 
internal bool canDock; 
internal DockingPosition position; 

This message gets synchronized over the network automatically, which we’ll cover next time. Any totem is a receiver of this message, can compare its ID and then know if it’s allowed to dock or not and if it’s docking into a gunner or pilot position.

Docking init self is also a model. It’s set up as a small state machine with the following states:

  • AllowedToDock
  • CurrentlyDocking
  • Docked
  • CurrentlyUndocking
  • Undocked

By default, the state is set to Undocked. Once the DockingStatus message is received, the state gets updated to the correct value: AllowedToDock if in range, Undocked if out of range. If you’re either in the Docked or AllowedToDock state, you can press a button to request dock or undock. This is again done with a message: RequestDock and RequestUndock.

They simply have a unique identifier of the totem who wants to dock, and the DockingPosition. This message is broadcast over the network, so it will be received by the owner of the titan. The titan will then decide if there’s a spot available in the docking position, it’ll send a ‘DockingProcedure’ message, saying if it’s possible or not. The totem will then know if it’s possible, and if this is the case, it will ask for a target by using a ‘RequestDockingTarget’ message. This is a message that’s only being sent locally, to the locally simulated titans. As the titan gameobject is the same across any client, the titan can run the gamelogic without having to verify this with the host. It will then have a position to move to (using a simple lerp) and once it’s docked, it has control over moving the titan or over the titan’s massive gun.

Here is an overview of all the messages and how they are being sent, and which ones are also sent over the network. In this case the DockingTarget is a model owned by the Titan.

image

Bonus code update

Hey there! I am Aldo Leka, and I’m currently working as an intern at Codeglue. It’s been a very exciting experience so far, because of challenges that fit my thinking style really well. I like problems that are deep and require careful and logical thinking for an extended period of time, and that are at the same time helpful to others. My latest task has been exactly like that.

The problem was that, while Niels tested Antegods with multiple other players, he could only see the output log file that Unity creates at each player’s computer after the game is exited. A lot of cluttered information comes from various logging systems in the game and it’s very hard to tell where something went wrong, specifically with networking.

Niels thought it would be really helpful to be able to save the incoming network messages from other players to a file, and when he wanted to ‘see’ what went wrong, he could simply load the file and all the messages would be loaded again chronologically, thus ‘simulating’ the networked game session. This way it would be easy to tell when something went wrong, and what this was about.

The type of messages that we need to simulate the most are the ones that contain gameplay data (compared to data for connectivity and such), including position, rotation, and especially custom data like creating a new totem or projectiles.

We already have a Network plugin, developed by Tom Jansen (co-employee at Codeglue), which is basically an interface, or a wrapper, of a full networking library. This plugin also adds Unity-related functionality such as synchronization of game objects’ creation, destruction, position, rotation, and scale and scene information. I analyzed this plugin, and the underlying network library, which is currently Lidgren, before attempting solutions to the problem.

I decided to use BinaryWriter to write a binary file with the incoming messages because it results in smaller size than a text file, and seems to work fine for my problem. And I observed that the incoming messages are polled from a queue which is filled by Lidgren internally.

My first attempt was to write all these data messages manually, by copying the way they are received in the data processing section of the Network plugin. I made a function to access a file and either write these data manually in it, or read it when the file is loaded. This resulted in a lot of duplicated code, which obviously is contrary to good OOP design and there was no way to write or read the custom messages, since I did not know what kind of data they contained (this is only known in the game code developed by Niels). I understood these afterwards, since I did not think that custom messages were as important as they are.

Here is some sample code of the first attempt:

static void AccessFile(bool isWriting = false) 
{ 
   byte messageId = isWriting ? NetStream.ReadByte(currentMessage) :fileReader.ReadByte(); 

   switch ((NetMessageType)messageId) 
   { 
      case NetMessageType.ClientInformation: // read or write specific data from or to the file. 
         AccessFileForClientInformation(isWriting); 
         break; // do this for every message type. 
   } 
}

My second attempt was to create a NetIncomingMessage from Lidgren in order to be able to process it when loading the file in the same part of the code as they are processed during a networking session, but I found out that this was not possible, since Lidgren does not allow the creation of a NetIncomingMessage from outside the library. I tried to change a few scope accessors in the library and could get it working that way, but as Niels suggested, we wanted independence from the network library since we might change it, so I decided to try a different way.

We already needed to wrap the Lidgren classes of NetIncomingMessage and NetOutgoingMessage for independence from the underlying networking library, and being in the need of creating NetIncomingMessage(s) myself, I decided to do the wrapping of both these classes in the Network plugin. That worked well. Afterwards, I decided to save the messages in a much simpler format:

[ReceiveTime][DataLength][Data bytes][MessageType]

At the same time, I added a flag to check whether simulation is happening, and when it is, the messages are going to be polled from the loaded file (which is internally implemented as a queue similar to Lidgren) and otherwise they are going to be polled from Lidgren as usual. This solves the issue of custom data too, since it doesn’t matter anymore what I save in the file as long as it’s in the format described above.

In this way, I learned a lot about multi-threading programming since I had to solve the consumer-producer problem needed for the simulation, which basically deals with pushing data (messages loaded from the file in my case) to a queue by another separate thread, and retrieving that data (de-queuing) from polling the messages by the main thread, in a thread-safe manner.

At the same time, I had to implement a precise timer to release the messages since the System.Timers.Timer had a mistake range of 40 milliseconds, which is not acceptable in our scenario. StopWatch works fine though, and I implemented it in a separate thread, as demonstrated below:

Stopwatch preciseTimer = new Stopwatch(); 
preciseTimer.Start(); 
double prev = 0.0; 
double now = 0.0; 
double accumulator = 0.0; 

while (isSimulating) 
{ 
   // we are interested in the milliseconds range. 
   now = 1000.0 * (double)preciseTimer.ElapsedTicks / Stopwatch.Frequency; 
   accumulator += now - prev; prev = now; 

   // make sure timeUntilNextMessages and the queue are accessed from one thread at a time. 
   lock (lockerObj) 
   { 
      // release messages at the received time. 
      if (accumulator >= timeUntilNextMessages) 
      { 
         accumulator = 0; 
         QueueNextMessages(); 
      } 
   }
} 

Thank you for reading up to this point, and please let us know what you think!

Antegods is supported by Dutch Cultural Media Fund and the MEDIA Programme of the European Union

imageimage

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: