Copper Mayhem

Copper Mayhem is a dungeon explorer game mixed with a bullet hell. This game is made with C# in MonoGame. It was made with a team of 4 developers. We wanted to challenge ourselves by making a 3D game in MonoGame. It was the first time I made a game in 3D.


My addition to this game are all the enemy’s behavior, so how the move. At first I created a follow behavior that follows the player. The only thing was the enemy followed the player too close so I needed to add a radius to the behavior so the enemy would follow from a distance. The code:

protected void FollowBehavior(GameTime gameTime)
        {
            Vector3 newFollowPosition = Position;
            Vector3 newFollowRotation = Rotation;

            newFollowRotation = MathHelper3D.GetRotationToPoint(Position, new Vector3(target.Position.X, target.Position.Y + 6, target.Position.Z));

            if (MathHelper3D.GetDistanceBetween2Points(Position, new Vector3(target.Position.X, target.Position.Y + 6, target.Position.Z)) > radius)
            {
                desiredVelocity = Vector3.Normalize(new Vector3(target.Position.X, target.Position.Y + 6, target.Position.Z) - Position) * maxSpeed;
                steering = desiredVelocity - enemyVelocity;
                if (steering.Length() > maxForce)
                {
                    steering = Truncate(steering, maxForce);
                }
                steering /= mass;
                enemyVelocity = Truncate(enemyVelocity + steering, maxSpeed);

                newFollowPosition += enemyVelocity * (float)gameTime.ElapsedGameTime.TotalSeconds;

            }
            Translate(newFollowPosition, newFollowRotation);
        }

After making the follow behavior I created the Orbit behavior which is basically a follow behavior but if the player is within the follow range it orbits around the player. The speed takes into account how close the player is to the enemy.
the code:

protected void OrbitBehavior(GameTime gameTime)
        {
            Vector3 newOrbitPosition = Position;
            Vector3 newOrbitRotation = Rotation;
            speed = MathHelper3D.GetDistanceBetween2Points(newOrbitPosition, target.Position) / orbitSpeed; // snelheid van de eyeball (hoe verder van de player hoe sneller de eyeball gaat)
            newOrbitRotation = MathHelper3D.GetRotationToPoint(Position, new Vector3(target.Position.X, target.Position.Y + 6, target.Position.Z));
            if (MathHelper3D.GetDistanceBetween2Points(newOrbitPosition, new Vector3(target.Position.X, target.Position.Y + 6, target.Position.Z)) > radius) // als de player buiten de radius is
            {
                {
                    desiredVelocity = Vector3.Normalize(new Vector3(target.Position.X, target.Position.Y + 6, target.Position.Z) - Position) * maxSpeed; // velocity waar de enemy heen wil
                    steering = desiredVelocity - enemyVelocity;
                    if (steering.Length() > maxForce)
                    {
                        steering = Truncate(steering, maxForce);
                    }
                    steering /= mass;
                    enemyVelocity = Truncate(enemyVelocity + steering, maxSpeed) + Velocity;
                    newOrbitPosition += enemyVelocity * (float)gameTime.ElapsedGameTime.TotalSeconds;
                }
            }
            else
            {
                newOrbitPosition = MovementHelper3D.Orbit(gameTime, newOrbitPosition, target.Position, Vector3.Up, MathHelper.ToRadians(20)); // Orbit rond player
                Position.Y += MovementHelper3D.Hover(gameTime, 3f, 7f, 0); // Hover in de y axis
            }
            Translate(newOrbitPosition, newOrbitRotation);

        }

It looked like this:

This was all not really hard to do so I wanted to challenge myself by learning how flocking behavior works and how I could add it to the game. I wanted to create a flock of bees. I first searched the main components of flocking behavior:

  • Separation, the bees want to seperate from ech other.
  • Alignment, the bees want to rotate to each others direction.
  • Cohesion, the bees want to go to each other.
  • Colliders, the bees dont want to bump in to objects.
  • *Extra* Exploration, the bees want to explore new places.

This was very hard for me, I didn’t know where to begin. I asked a student teacher who had introduced me to flocking where I could begin. He started to explain to me what a dot product is and explained how it works. So what I did is finding which bees are in the dot product of a bee and looking at their variables.
After that I began to make Alignment. By trying to let a bee compare and turn to other bees in the dot product. It took me a while to figure out, I watched many tutorials and read many sites. Eventually I got it. Did the same thing for Alignment ,Cohesion and Colliders. WARNING the code is not optimized and not pretty at all. The code:

protected void FlockingBehavior(GameTime gameTime)
        {
            List<Vector3> sharedVelocityList = new List<Vector3>(); // list for every velocity in the dot product.
            List<Vector3> nearbyPositions = new List<Vector3>();
            List<Vector3> nearbyObjects = new List<Vector3>(); // list for every position in the enemy list.
            Vector3 averageVelocity = Vector3.Zero;
            Vector3 averagePosition = Vector3.Zero;
            Vector3 difference = Vector3.Zero;
            Vector3 newPosition = Vector3.Zero;
            

            if (Parent is GameObjectList gameObjectList)
            {
                foreach (GameObject child in gameObjectList.children)
                {
                    if (child is Bee threeDChild && threeDChild != this)
                    {
                        if (MathHelper3D.GetDistanceBetween2Points(Position, threeDChild.Position) < searchRadius)
                        {
                            BoundingBox childBox = threeDChild.BoundingBox;
                            float dotProduct = Vector3.Dot(Position, threeDChild.Position); // zorgt ervoor dat de enemy niet achter zich kan kijken                                if(dotProduct > 0.4f)
                            {
                                if (dotProduct > 0.4f)
                                {
                                    sharedVelocityList.Add(threeDChild.enemyVelocity);
                                    nearbyPositions.Add(threeDChild.Position);
                                    
                                }
                            }
                        }
                    }
                    if(child is ThreeDGameObject threeDObject && threeDObject != this)
                    {
                        if (MathHelper3D.GetDistanceBetween2Points(Position, threeDObject.Position) < searchRadius)
                        {
                            float dotProduct = Vector3.Dot(Position, threeDObject.Position); // zorgt ervoor dat de enemy niet achter zich kan kijken
                            if (dotProduct > 0.4f) 
                            {
                            nearbyObjects.Add(threeDObject.Position);
                            }
                        }
                    }
                }
            }

            foreach (Vector3 velocity in sharedVelocityList)
            {
                averageVelocity += velocity; // Gemiddelde velocity van alle enemies
            }

            if (sharedVelocityList.Count > 0)
            {
                //Alignment
                averageVelocity /= sharedVelocityList.Count;
                alignmentSteering = averageVelocity;
                alignmentSteering -= enemyVelocity;
                if (alignmentSteering.Length() > maxForce)
                {
                    alignmentSteering = Truncate(alignmentSteering, maxForce);
                }
                alignmentSteering /= mass;
                alignment = alignmentSteering;
                sharedVelocityList.Clear();
            }

            foreach (Vector3 Position in nearbyPositions)
            {
                averagePosition += Position; // Gemiddelde positie van alle enemies
            }

            if (nearbyPositions.Count > 0)
            {
                //Cohesion
                averagePosition /= nearbyPositions.Count;
                cohesionSteering += averagePosition;
                cohesionSteering -= Position;
                cohesionSteering -= enemyVelocity;
                if (cohesionSteering.Length() > maxForce)
                {
                    cohesionSteering = Truncate(cohesionSteering, maxForce);
                }
                cohesionSteering /= mass;
                cohesion = cohesionSteering;
            
            }

            if(nearbyObjects.Count > 0)
            {
            //Separation
            foreach (Vector3 Position in nearbyObjects)
            {
                difference = Position - this.Position ;
                difference *= 1- MathHelper3D.GetDistanceBetween2Points(Position, this.Position);
                separationSteering += difference;
            
            separationSteering /= nearbyObjects.Count;
            separationSteering -= enemyVelocity;
            if (separationSteering.Length() > maxForceSeparation)
            {
                separationSteering = Truncate(separationSteering, maxForceSeparation);
            }
            separationSteering /= mass;
            separation = separationSteering;
            }
            nearbyObjects.Clear();
            }

            //follow target
            newPosition += new Vector3(target.Position.X, target.Position.Y + 8, target.Position.Z);
            steering += newPosition;
            steering -= Position;
            steering -= enemyVelocity;
            if (steering.Length() > maxForce)
            {
                steering = Truncate(steering, maxForceFollowtarget);
            }
            steering /= mass;
            followTarget = steering;
        }

I put the code all in 1 function because at the time I couldn’t figure out how to use more than 1 scripts for 1 game object.
This was the result after tweaking a lot of variables and testing (also in an early version of the game):

I was really really proud of this and still am, but if I would make with my knowledge now, I would firstly make it in Unity, because Unity works a lot simpeler and better than MonoGame. Also I would make it in seperate files and more optimized.
I wanted to make a quadtree, but didn’t have enough time, because I needed to hand in my work.

I and an other team mate found al the models for our game so I did some of that aswell

But here is some gameplay of our game:

https://youtu.be/p1eog_OtdEw