Steffan's Game Development Portfolio

See Projects Go to git repositories
Red Barron World War I Flight Simulator
November 2016 - January 2017

During the second semester of the second year I worked on a VR project with all my classmates. The Red Barron is a VR experience that makes use of the HTC Vive and a Playseat

Me and all my classmates made an AI for the enemy planes in the game, beside that I also provided the objective system for the player that is displays the objectives on a note in the cockpit of the plane.

When the game was done we ran into one major problem, the engines of the seat gave off an electromagnetic radiation that interfered with the connection between the vive and the little boxes that helped the vive determine its position. Whenever we enabled the engines underneath the playseat the vive would stop working. After trying a lot of things to fix it we thought the computer might have been a problem and was having trouble controlling both the HTC Vive and the engines of the Seat. I wrote a small network bridge that allowed the game to be executed on computer one, which also controlled the vive, While sending the data for the engines to another computer that controlled the engines. In the end this sadly did not fix the problem and that's when we realised the problem must me the radiation from the engines.

Red Barron

Red Barron Playing

Plane AI
The plane AI is a script we all had to make, there were 20 different AI systems in the game so each plane had it's own behaviour. The AI below is the one I wrote for the Red Barron Game
SteffanAI.cs
  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4.  
  5. public class SteffanAI : PlaneBase {
  6.     // Different phases for the AI
  7.     public enum AIBehaviour {
  8.         TakeOff,
  9.         FlyingToTarget,
  10.         Attacking,
  11.         AvoidingObstacle,
  12.         LookingForTarget
  13.     }
  14.     public AIBehaviour aiBehaviour = AIBehaviour.TakeOff;
  15.     public float obstacleScanningRange = 150;
  16.     public float enemyScanningRange = 300;
  17.     public float desiredHeight = 400;
  18.     public float turboMinDistance = 75;
  19.     public float turboMaxAngle = 5;
  20.     public float shootingMaxAngle = 5F;
  21.     public float shootingRange = 200;
  22.     public float formationFollowDistance = 75;
  23.     public Health target = null;
  24.  
  25.     public override void Update()
  26.     {
  27.         base.Update();
  28.         airBrake = false;
  29.         // Check the current AIBehaviour phase and execute the tasks that come with that phase, check if it's time to move to anothe phase
  30.         switch (aiBehaviour) {
  31.             case AIBehaviour.TakeOff:
  32.                 throttle = 1;
  33.                 pitch = -1;
  34.                 if (Altitude > 5)
  35.                     aiBehaviour = AIBehaviour.LookingForTarget;
  36.                 break;
  37.             case AIBehaviour.LookingForTarget:
  38.                 if (followPlane != null && !followPlane.dead)
  39.                 {
  40.                     Vector3 followPosition = (-followPlane.transform.forward) * formationFollowDistance + followPlane.transform.position;
  41.                     Vector3 followForm = formationFollow(followPosition);
  42.                     pitch = followForm.x;
  43.                     yaw = followForm.y;
  44.                     if (followForm.z > 0)
  45.                     {
  46.                         Turbo();
  47.                     }
  48.                     else {
  49.                         airBrake = true;
  50.                     }
  51.                 }
  52.                 else {
  53.                     Vector3 follow = followTarget(startPos);
  54.                     pitch = follow.x;
  55.                     yaw = follow.y;
  56.                     if (follow.z > 0)
  57.                     {
  58.                         Turbo();
  59.                     }
  60.                 }
  61.                 target = FindTarget();
  62.                 if (target != null)
  63.                     aiBehaviour = AIBehaviour.FlyingToTarget;
  64.                 break;
  65.             case AIBehaviour.FlyingToTarget:
  66.                 Vector3 follow2 = followTarget(target.transform.position);
  67.                 pitch = follow2.x;
  68.                 yaw = follow2.y;
  69.                 if (follow2.z > 0)
  70.                 {
  71.                     Turbo();
  72.                     airBrake = false;
  73.                 }
  74.                 else if (follow2.z < 0)
  75.                 {
  76.                     airBrake = true;
  77.                 }
  78.                 else {
  79.                     airBrake = false;
  80.                 }
  81.                 if (ShootToKill()) {
  82.                     FireGuns();
  83.                 }
  84.                 if (target == null || target.dead || target.crashed) {
  85.                     target = null;
  86.                     aiBehaviour = AIBehaviour.LookingForTarget;
  87.                 }
  88.                 break;
  89.         }
  90.         if (aiBehaviour != AIBehaviour.TakeOff) {
  91.             Vector3 avoid = avoidObstacles();
  92.             if (avoid != Vector3.zero)
  93.             {
  94.                 pitch = -avoid.x;
  95.                 yaw = -avoid.y;
  96.             }
  97.         }
  98.     }
  99.  
  100.     //Level out the plane
  101.     float levelPlane() {
  102.         Vector3 lookingPoint = transform.forward * desiredHeight;
  103.         lookingPoint = new Vector3(lookingPoint.x, desiredHeight - transform.position.y, lookingPoint.z).normalized;
  104.  
  105.         float dot = Vector3.Dot(transform.up, lookingPoint)*3;
  106.         print(dot);
  107.         return dot * -1;
  108.     }
  109.  
  110.     // Fire guns when an enemy is on the range of the guns
  111.     bool ShootToKill() {
  112.         if (target != null && !target.dead) {
  113.             float angle = Vector3.Angle(target.transform.position - transform.position, transform.forward);
  114.             if (angle < shootingMaxAngle && Vector3.Distance(target.transform.position, transform.position) < shootingRange) {
  115.                 return true;
  116.             }
  117.         }
  118.         return false;
  119.     }
  120.  
  121.     //Find a target, checks all the planes in a certain radius and then checks which one poses the biggest threat
  122.     Health FindTarget() {
  123.         Collider[] hitColliders = Physics.OverlapSphere(transform.position, enemyScanningRange, 1<<8);
  124.         Health returnValue = null;
  125.         if (hitColliders.Length > 0) {
  126.             List<PlaneBase> enemyPlanes = new List<PlaneBase>();
  127.             foreach (Collider c in hitColliders) {
  128.                 PlaneBase pb = c.gameObject.GetComponent<PlaneBase>();
  129.                 if (pb != null && pb.team != team) {
  130.                     enemyPlanes.Add(pb);
  131.                 }
  132.             }
  133.             if (enemyPlanes.Count == 1)
  134.             {
  135.                 returnValue = enemyPlanes[0];
  136.             }
  137.             else if (enemyPlanes.Count > 1)
  138.             {
  139.                 float closestAngle = 180;
  140.                 PlaneBase closestPlane = null;
  141.                 foreach (PlaneBase pb in enemyPlanes)
  142.                 {
  143.                     float angle = Vector3.Angle((transform.position - pb.transform.position), pb.transform.forward);
  144.                     if (angle < closestAngle)
  145.                     {
  146.                         closestPlane = pb;
  147.                         closestAngle = angle;
  148.                     }
  149.                 }
  150.                 returnValue = closestPlane;
  151.             }
  152.             else {
  153.                 List<Health> hList = new List<Health>();
  154.                 foreach (Collider c in hitColliders) {
  155.                     Health h = c.gameObject.GetComponent<Health>();
  156.                     if (h != null && h.team != team) {
  157.                         hList.Add(h);
  158.                     }
  159.                 }
  160.                 if (hList.Count > 0) {
  161.                     returnValue = hList[0];
  162.                 }
  163.             }
  164.         }
  165.         return returnValue;
  166.     }
  167.  
  168.     //Follow the leader of a formation, returns a vector3 that contains the data for the Roll, yaw and pitch
  169.     Vector3 formationFollow(Vector3 formationPosition) {
  170.         Vector3 returnVector = new Vector3(0, 0, 0);
  171.         Vector3 other = (formationPosition - transform.position).normalized;
  172.         float pitchDot = Vector3.Dot(transform.up, other);
  173.         float yawDot = Vector3.Dot(transform.right, other);
  174.         float forwardDot = Vector3.Dot(transform.forward, other);
  175.  
  176.         returnVector = new Vector3((pitchDot * 3 * -1), 0, 0);
  177.         returnVector = new Vector3(returnVector.x, yawDot * 3, 0);
  178.  
  179.         if (forwardDot > 0)
  180.         {
  181.             returnVector = new Vector3(returnVector.x, returnVector.y, 1);
  182.         }
  183.         else {
  184.             returnVector = new Vector3(returnVector.x, returnVector.y, -1);
  185.         }
  186.         return returnVector;
  187.     }
  188.  
  189.     //Follow an enemy target, returns a vector3 that contains the data for the Roll, yaw and pitch
  190.     Vector3 followTarget(Vector3 targetPosition) {
  191.         Vector3 returnVector = new Vector3(0,0,0);
  192.         Vector3 other = (targetPosition - transform.position).normalized;
  193.         float pitchDot = Vector3.Dot(transform.up, other);
  194.         float yawDot = Vector3.Dot(transform.right, other);
  195.         float forwardDot = Vector3.Dot(transform.forward, other);
  196.         if (forwardDot < 0)
  197.         {
  198.             returnVector = new Vector3(-1, 0, 0);
  199.         }
  200.         else {
  201.             returnVector = new Vector3((pitchDot * 3 * -1), 0, 0);
  202.         }
  203.         returnVector = new Vector3(returnVector.x, yawDot * 3, 0);
  204.         if (Vector3.Distance(targetPosition, transform.position) > turboMinDistance && Vector3.Angle((targetPosition - transform.position), transform.forward) < turboMaxAngle) {
  205.             returnVector = new Vector3(returnVector.x, returnVector.y, 1);
  206.         } else if (Vector3.Distance(targetPosition, transform.position) < turboMinDistance) {
  207.             returnVector = new Vector3(returnVector.x, returnVector.y, -1);
  208.         }
  209.         return returnVector;
  210.     }
  211.  
  212.     // checks the area around the pain using raycasts, returns a vector3 that contains the data for the Roll, yaw and pitch
  213.     Vector3 avoidObstacles() {
  214.         Vector3 returnVector = Vector3.zero;
  215.         Vector3 originPoint = new Vector3(transform.position.x, transform.position.y, transform.position.z);
  216.         //Forward and a bit lower
  217.         if (Physics.Raycast(originPoint, transform.forward, obstacleScanningRange) || Physics.Raycast(originPoint, (transform.forward - transform.up + transform.forward).normalized, obstacleScanningRange))
  218.         {
  219.             Debug.DrawRay(originPoint, transform.forward * obstacleScanningRange, Color.red);
  220.             Debug.DrawRay(originPoint, (transform.forward - transform.up + transform.forward).normalized * obstacleScanningRange, Color.red);
  221.             returnVector = new Vector3(1, 0, 0);
  222.         }
  223.         else {
  224.             if (Physics.Raycast(originPoint, (transform.forward + transform.up).normalized, obstacleScanningRange))
  225.             {
  226.                 Debug.DrawRay(originPoint, (transform.forward + transform.up).normalized * obstacleScanningRange, Color.red);
  227.                 returnVector = new Vector3(-1, 0, 0);
  228.             }
  229.             else {
  230.                 Debug.DrawRay(originPoint, (transform.forward + transform.up).normalized * obstacleScanningRange, Color.green);
  231.             }
  232.             Debug.DrawRay(originPoint, transform.forward * obstacleScanningRange, Color.green);
  233.             Debug.DrawRay(originPoint, (transform.forward - transform.up + transform.forward).normalized * obstacleScanningRange, Color.green);
  234.         }
  235.  
  236.         if (Physics.Raycast(originPoint, (transform.forward + transform.right).normalized, obstacleScanningRange * 0.5F))
  237.         {
  238.             returnVector = new Vector3(returnVector.x, 1, 0);
  239.             Debug.DrawRay(originPoint, (transform.forward + transform.right).normalized * (obstacleScanningRange * 0.5F), Color.red);
  240.         }
  241.         else {
  242.             Debug.DrawRay(originPoint, (transform.forward + transform.right).normalized * (obstacleScanningRange * 0.5F), Color.green);
  243.         }
  244.  
  245.         if (Physics.Raycast(originPoint, (transform.forward - transform.right).normalized, obstacleScanningRange * 0.5F))
  246.         {
  247.             Debug.DrawRay(originPoint, (transform.forward - transform.right).normalized * (obstacleScanningRange * 0.5F), Color.red);
  248.             if (returnVector.y != 0)
  249.             {
  250.                 returnVector = new Vector3(1, 0, 0);
  251.             }
  252.             else {
  253.                 returnVector = new Vector3(returnVector.x, -1, 0);
  254.             }
  255.         }
  256.         else {
  257.             Debug.DrawRay(originPoint, (transform.forward - transform.right).normalized * (obstacleScanningRange * 0.5F), Color.green);
  258.         }
  259.         return returnVector;
  260.     }
  261. }
  262.  
SteffanAI.cs