Let’s make a multiplayer game (part 9)

In this article I am going to cover the handling of enemy ships. The first thing to consider when adding a new piece of functionality to the game is where the responsibilities lie. Based on the classification discussed in article 5 we can classify the creation of enemies and enemy shots fired at the player as the responsibility of the server. All other enemy behaviour is handled on both the client and server side.

Managing Enemies

Adding enemies follows exactly the same pattern as the previous game logic. We define an Enemy class that inherits from the Sprite class. The Enemy class is responsible for handling enemy movement across the screen and the rendering of the enemy sprite. The EnemyManager class is responsible for the creation of enemies and whether an enemy has fired at the player. These activities happen on the server with the client-side merely adding enemies created on the server to the local list.

public void Update(GameTime gameTime)
        
            for (int x = this.enemies.Count – 1; x >= 0; x–)
            
                this.enemies[x].Update(gameTime);
                if (this.enemies[x].IsActive == false)
                
                    this.enemies.RemoveAt(x);
                
                else
                
                    if (this.isHost)
                    
                        if ((float)this.randomNumberGenertor.Next(0, 1000) / 10 <= this.shipShotChance)
                        
                            Vector2 fireLoc = this.enemies[x].SimulationState.Position;
                            fireLoc += this.gunOffset;

                            Vector2 shotDirection =
                                this.playerManager.Players.ToList()[
                                    this.randomNumberGenertor.Next(0, this.playerManager.Players.Count() – 1)].Center
                                – fireLoc;
                            shotDirection.Normalize();

                            this.shotManager.FireShot(fireLoc, shotDirection, this.enemies[x].Id, false);
                        
                    
                
            

            if (this.isActive && this.isHost)
            
                this.UpdateWaveSpawns();
            
        

In the above Update method of the EnemyManager class we loop through the enemy object instances and call their Update methods. If an enemy instance is no longer active (due to it moving out of the screen bounds or being destroyed) we remove it from the local list of enemies.

Note: This happens on both the server and client side without any updates from the server to the client. this is due to the deterministic nature of the simulation.

Next only the server side will do a check to see if an enemy has fired at a randomly selected player. This code makes use of the ShotManager class to create the shot and to notify the client that a shot was fired.

Lastly, the server side will spawn a new wave of enemies based on the configured game timer. The UpdateWaveSpawns method makes use of the server-side SpawnEnemy method which will raise an event for the message to be sent to the client-side.

Sending Messages

As in the previous articles we attach an event handler to the EnemyManager class on the server-side as follows.

this.enemyManager = new EnemyManager(
    randomNumberGenerator, this.shotManager, this.playerManager, this.IsHost);
if (this.IsHost)

    this.enemyManager.EnemySpawned +=
        (sender, e) => this.networkManager.SendMessage(new EnemySpawnedMessage(e.Enemy));

In the above code we send the EnemySpawned game message to all the connected clients if the code is running as the server.

public class EnemySpawnedMessage : IGameMessage

    #region Constructors and Destructors

    public EnemySpawnedMessage(NetIncomingMessage im)
    
        this.Decode(im);
    

    public EnemySpawnedMessage(Enemy enemy)
    
        this.Id = enemy.Id;
        this.Position = enemy.SimulationState.Position;
        this.Velocity = enemy.SimulationState.Velocity;
        this.Rotation = enemy.SimulationState.Rotation;
        this.MessageTime = NetTime.Now;
        this.Path = enemy.Path;
    

    #endregion

    #region Public Properties

    public long Id get; set;

    public double MessageTime get; set;

    public GameMessageTypes MessageType
    
        get
        
            return GameMessageTypes.EnemySpawned;
        
    

    public int Path get; set;

    public Vector2 Position get; set;

    public float Rotation get; set;

    public Vector2 Velocity get; set;

    #endregion

    #region Public Methods and Operators

    public void Decode(NetIncomingMessage im)
    
        this.Id = im.ReadInt64();
        this.MessageTime = im.ReadDouble();
        this.Position = im.ReadVector2();
        this.Velocity = im.ReadVector2();
        this.Rotation = im.ReadSingle();
        this.Path = im.ReadInt32();
    

    public void Encode(NetOutgoingMessage om)
    
        om.Write(this.Id);
        om.Write(this.MessageTime);
        om.Write(this.Position);
        om.Write(this.Velocity);
        om.Write(this.Rotation);
        om.Write(this.Path);
    

    #endregion

The EnemySpawned game message follows exactly the same pattern as all the other game message classes. I am only including it to reinforce the pattern. There is nothing different in terms of multiplayer programming.

Receiving Messages

The receipt and processing of messages follows the same pattern as before and the ProcessNetworkMessages method is modified as follows.

case NetIncomingMessageType.Data:
    var gameMessageType = (GameMessageTypes)im.ReadByte();
    switch (gameMessageType)
    
        case GameMessageTypes.UpdateAsteroidState:
            this.HandleUpdateAsteroidStateMessage(im);
            break;
        case GameMessageTypes.UpdatePlayerState:
            this.HandleUpdatePlayerStateMessage(im);
            break;
        case GameMessageTypes.ShotFired:
            this.HandleShotFiredMessage(im);
            break;
        case GameMessageTypes.EnemySpawned:
            this.HandleEnemySpawnedMessage(im);
            break;
    

    break;

The HandleEnemySpawnedMessage method is used to create the enemy instance on the client-side.

private void HandleEnemySpawnedMessage(NetIncomingMessage im)

    var message = new EnemySpawnedMessage(im);

    var timeDelay = (float)(NetTime.Now – im.SenderConnection.GetLocalTime(message.MessageTime));

    Vector2 adjustedPosition = message.Position + (message.Velocity * timeDelay);

    this.enemyManager.SpawnEnemy(message.Id, message.Path, adjustedPosition, message.Velocity, message.Rotation);

The above code makes use of the client-side version of the SpawnEnemy method on the EnemyManager. This is important since we don’t want to send game messages in this case.

We did not introduce any additional code to handle the enemies firing at the player. This is because we could reuse the ShotManager class introduced in the previous article.

The following video shows an example of the new enemy functionality in action.

Asteroids with Enemies (200ms delay)
find the cost of your paper

Sep 13, Grand Remembrances

Today is Grandparents Day in the United States. Being a Grand is a special honor. I feel very blessed that my wife and I have two grandchildren. We were able to visit them today. Yes, we are still being cautious with the coronavirus, but we also find it very difficult to not see them when they live so close. So today we did drop by to visit Jacob (age 10) and Sophia (age 7) along with their parents. We brought donuts and caught up with them. Our grandchildren are still pretty young and this is a precious time in their lives – and ours!

I wish I had known my grandparents better. We never lived in the same place. Dad was a career Air Force pilot, so we moved around a lot. But we did get to see them once in a while when they would visit us, or we them.

A Plague of Giants

There are five known magical ‘kennings’ or types: air, water, fire, earth, and plants. Each nation specializes in of these kennings, and the magic influences the society. There’s a big pitfall with this diversity of ability and locale–not everyone gets along.

Enter the Hathrim giants, or ‘lavaborn’ whose kenning is fire. Where they live the trees that fuel their fire are long gone, but the giants are definitely not welcome anywhere else. They’re big, they’re violent, and they’re ruthless. When a volcano erupts and they are forced to evacuate, they take the opportunity to relocate. They don’t care that it’s in a place where they aren’t wanted.

I first read Kevin Hearne’s Iron Druid books and loved them (also the quirky The Tales of Pell), so was curious about this new venture, starting with A PLAGUE OF GIANTS. Think Avatar: The Last Airbender meets Jim Butcher’s Codex Alera series. Elemental magic, a variety of races, different lands. And it’s all thrown at you from page one.

But this story is told a little differently. It starts at the end of the war, after a difficult victory, and a bard with earth kenning uses his magic to re-tell the story of the war to a city of refugees. And it’s this movement back and forth in time and between key players in this war that we get a singularly grand view of the war as a whole. Hearne uses this method to great effect.

There are so many interesting characters in this book that I can’t cover them all here. Often in books like this such a large cast of ‘main’ character can make the storytelling suffer, especially since they don’t have a lot of interaction with each other for the first 3/4 of the book–but it doesn’t suffer, thankfully. And the characterization is good enough, despite these short bursts, that by the end we understand these people and care about what happens to them.

If there were a main character it would be Dervan, a historian who is assigned to record (also spy on?) the bard’s stories. He finds himself caught up in machinations he feels unfit to survive. Fintan is the bard from another country, who at first is rather mysterious and his true personality is hidden by the stories he tells; it takes a while to understand him. Gorin Mogen is the leader of the Hathrim giants who decide to find a new land to settle. He’s hard to like, but as far as villains go, you understand his motivations and he can be even a little convincing. There’s Abhi, the son of hunters, who decides hunting isn’t the life for him–and unexpectedly finds himself on a quest for the sixth kenning. And Gondel Vedd, a scholar of linguistics who finds himself tasked with finding a way to communicate with a race of giants never seen before (definitely not Hathrim) and stumbles onto a mystery no one could have guessed: there may be a seventh kenning.

There are other characters, but what makes them all interesting is that they’re regular people (well, maybe not Gorin Mogen or the viceroy–he’s a piece of work) who become heroes in their own little ways, whether it’s the teenage girl who isn’t afraid to share vital information, to the scholars who suddenly find how crucial their minds are to the survival of a nation, to the humble public servants who find bravery when they need it most. This is a story of loss, love, redemption, courage, unity, and overcoming despair to not give up. All very human experiences by simple people who do extraordinary things.

Hearne’s worldbuilding is engaging. He doesn’t bottle feed you, at first it feels like drinking from a hydrant, but then you settle in and pick up things along the way. Then he shows you stuff with a punch to the gut. This is no fluffy world with simple magic without price. All the magic has a price, and more often than not it leads you straight to death’s door. For most people just the seeking of the magic will kill you. I particularly enjoyed the scenes with Ahbi and his discovery of the sixth kenning and everything associated with it. But giants? I mean, really? It isn’t bad enough fighting people who can control fire that you have to add that they’re twice the size of normal people? For Hearne if it’s war, the stakes are pretty high, and it gets ugly.

The benefit of the storytelling style is that the book, despite its length, moves along steadily (Hearne is no novice, here). The bits of story lead you along without annoying cliffhangers (mostly), and I never got bored with the switch between characters. It was easy to move between them, and they were recognizable enough that I got lost or confused. The end of the novel felt a little abrupt, but I guess that has more to do with I was ready for the story to continue, despite the exiting climax.

If you’re looking for epic fantasy with fun storytelling and clever worldbuilding, check out A PLAGUE OF GIANTS.

The post A Plague of Giants appeared first on Elitist Book Reviews.

The Artwork Of Gary Choo

Gary Choo is a concept artist/illustrator based in Singapore. I’ve know Gary for a good many years ( 17, actually ), working together in animation studios in Singapore like Silicon Illusions and Lucasfilm. Gary currently runs an art team at Mighty Bear Games, but when time allows he also draws covers for Marvel comics, and they’re amazing –

The Art Of Gary Choo
The Art Of Gary Choo
The Art Of Gary Choo
The Art Of Gary Choo
The Art Of Gary Choo

To see more of Gary’s work or to engage him for freelance work, head down to his ArtStation.

The post The Art Of Gary Choo appeared first on Halcyon Realms – Art Book Reviews – Anime, Manga, Film, Photography.

27