Godling

Godling was our third and final gameproject at FutureGames Academy.

 

In Godling you play as a god without any believers and your goal is to gather followers. By controlling your prophet you must travel the land and search for people that you then bring to your settlement.

 

When designing Godling we wanted to try and make an incrimental progression game. Leading followers to your settlement makes them construct buildings and which gains you passive income of Godpower points. Godpower points can then be spent to upgrade the buildings further to increase your income. When you have reached enough Godpower you finish the level and can progress through the game.

 

The game was developed in collaboration with Might and Delight (Shelter & Shelter 2). The premise was to make a game similar to H.C Andersens' The Ugly Duckling. This here represented in a powerless and despised god slowly gaining back favor.

My roles in the project

  • Designing, scripting and implementing GodPower functionality
  • Implementing and scripting GodPower selection
  • Designing, scripting and implementing puzzles in the world
  • Implementing and scripting character animations
  • Optimisation

 

The terraforming mechanic allows the player to lower/raise the ground to traverse environmental hazards, such as water, and create new paths for the followers.

using UnityEngine;
using System.Collections;

public class Terraforming : MonoBehaviour {

	public ParticleSystem smokeParticle;
    public bool isDirty;
    public LevelBlueprint lvlBp;

	float radius = 4f;
	Mesh mesh;
	MeshCollider meshCollider;
	Vector3[] targetPos;
	float force = 30f;
	float[] startVerticePoints;
	AudioSource terraformSound;
    GodCameraMove god;
	GodPowerController godController;
	PlayerController playerController;

    //Initialize components

	void Awake () {
        god = GameObject.FindGameObjectWithTag("MainCamera").GetComponent();
		terraformSound = GameObject.FindGameObjectWithTag ("MainCamera").GetComponent ();
		terraformSound.mute = true;
		mesh = GetComponent ().mesh;
		GetComponent  ().sharedMesh = mesh;
		meshCollider = GetComponent  ();
		Vector3[] meshVertices = mesh.vertices;
		startVerticePoints = new float[mesh.vertexCount];
        lvlBp = FindObjectOfType();
		for (int i = 0; i < mesh.vertexCount; i++) {
			startVerticePoints[i] = meshVertices[i].y;
		}
	}

    //Find godpowercontroller and playercontroller

	void Start(){
        godController = GameObject.FindGameObjectWithTag("godpower_controller").GetComponent();
        playerController = GameObject.FindGameObjectWithTag("player_controller").GetComponent();
	}

    //Check if button is held down. If true, raycast and play sound

	void Update () {
        if (playerController != null && godController != null)
        {
            if (Input.GetKey(playerController.select) && godController.godPowers == GodPowerController.GodPowers.Terraforming)
            {

                Raycast();
                terraformSound.mute = false;

            }

            if (Input.GetKeyUp(playerController.select))
            {
                terraformSound.mute = true;
            }
        }
	}

    //Raycast at mouse hit. If within influence area, run TerraformAtLocation

	void Raycast()
	{
		RaycastHit hitInfo;
		Ray rayCast = Camera.main.ScreenPointToRay(Input.mousePosition);
        if (god.DeactivateLayerRaycast(rayCast, out hitInfo))
        {
            if (meshCollider.Raycast(rayCast, out hitInfo, Mathf.Infinity))
            {
                Vector3 localHitPoint = hitInfo.point;
                if (Input.GetKey(playerController.terraformToggle))
                {
                    
                    foreach (Terraforming t in lvlBp.terraformingScripts)
                    {

                        t.TerraformAtLocation(localHitPoint, 1 * Time.deltaTime, force);
                    }
                    Camera.main.GetComponent().Shake(0.15f);
                }
                else
                {
                    foreach (Terraforming t in lvlBp.terraformingScripts)
                    {

                        t.TerraformAtLocation(localHitPoint, -1 * Time.deltaTime, force);
                    }
                    
                    Camera.main.GetComponent().Shake(0.15f);
                }
            }

        }
	}

    //Check if object is within desired area. If true, offset vertices in mesh close to raycast up/down depending on modifier.
    //Also apply to nearby objects on grid.
    //If mesh is dirty, run update mesh to recalculate normals and bounds.
     

	public void TerraformAtLocation(Vector3 pos, float magnitude, float force)
	{
        pos = transform.InverseTransformPoint(pos);
        
        if(pos.sqrMagnitude > 80 * 80){
           return;
        }

		targetPos = mesh.vertices;


        
		for (int i = 0; i < targetPos.Length; i++) {
            
			float dist = FlatDistance (pos, targetPos[i]);
			float yOffset = Mathf.Max(0f, radius - dist);
			targetPos[i].y -= yOffset * magnitude * force;
			targetPos[i].y = Mathf.Clamp (targetPos[i].y, startVerticePoints[i], startVerticePoints[i] + 5);
		}


		if (targetPos != null) {
			Vector3[] meshVertices = mesh.vertices;
			for (int i = 0; i < meshVertices.Length; i++) {
				meshVertices [i].y = Mathf.Lerp (meshVertices [i].y, targetPos [i].y, 0.1f);
				if(Mathf.Abs (targetPos [i].y - meshVertices [i].y) > 0.01f){

				}
			}

            isDirty = true;

			UpdateMesh (meshVertices);
		}
	}

	float FlatDistance (Vector3 a, Vector3 b){
		a.y = 0;
		b.y = 0;
		return Vector3.Distance (a, b);
	}	

    //Recalculate normals if mesh is dirty

	void UpdateMesh(Vector3[] verts){
		mesh.vertices = verts;
		mesh.RecalculateNormals ();
		mesh.RecalculateBounds ();
		
		
		
	}

    //Update collision of mesh, mesh is now clean

    public void UpdateCollision()
    {
        meshCollider.enabled = false;
        meshCollider.enabled = true;
        isDirty = false;
    }
}

PriestMove controls the majority of the actions the priest takes when the player directs him. This includes NavMesh movement.

using UnityEngine;
using System.Collections;
using DG.Tweening;
using System.Collections.Generic;

public class PriestMoveTo : MonoBehaviour
{

    
	[HideInInspector]
	public float walkDistance;

	[HideInInspector]
	public float recruitedAgents;
    
    

    public bool wantToIdlePatrol = false;

    public GameObject floatingTextPrefab;

    

    [HideInInspector]
    public NavMeshAgent agent;

    [HideInInspector]
    public bool isDispersing = false;

    PriestRespawn priestRespawner;
    WinBox winBox;
    PlayerController playerController;
    GodCameraMove god;
    bool isInCS = false;
    MeshRenderer mR;
    float lastCommandTime;
    Vector3 lastRaycastHitPosition;
    Vector3 startIdlePos;
    Vector3 startPos;
    AudioSource audioSource;
    int randAudio;
    List waypoints = new List();

    //Initialize components
    void Awake()
    {
        agent = GetComponent();
    }

    //Initialize components
    void Start()
    {
        priestRespawner = FindObjectOfType();
        audioSource = gameObject.GetComponent();
        god = GameObject.FindObjectOfType();
        mR = GetComponent();
        startPos = transform.position;
        playerController = GameObject.FindObjectOfType();
    }

  
    //Check for button click. If clicked, raycasts and sets destination for NavMeshAgent to raycast.hit

    void Update()
    {

        Vector3 distFromDes = transform.position - agent.destination;

        if (gameObject.tag == "priest")
        {
            if (Input.GetKey(playerController.followerMove))
            {
                SetDelayedRaycastHitPosition();
            }
            Ray ray = GodCameraMove.mainCamera.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;

            


            if (Input.GetKey(playerController.followerMove) && god.TerrainLayerRaycast(ray, out hit))
            {
                if (CanReceiveDestination())
                {
                        
                        agent.destination = hit.point;
                        lastCommandTime = Time.time;
                }

            }
            else
            {
                if (lastRaycastHitPosition != Vector3.zero)
                {
                       

                }
            }

        }

        if (distFromDes.magnitude > 1) {
			
			walkDistance += 1;
		}

    }


    float TimeSinceLastCommand()
    {
        return Time.time - lastCommandTime;
    }

    //Checks if the agent can recieve a new destination
    bool CanReceiveDestination()
    {
        return agent != null && agent.enabled && agent.isOnNavMesh && !isInCS;
    }


    //Saves the latest raycast position
    public void SetDelayedRaycastHitPosition()
    {
        Ray ray = GodCameraMove.mainCamera.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;

        if (god.TerrainLayerRaycast(ray, out hit))
        {
            lastRaycastHitPosition = hit.point;
        }
    }

}

The wind godpower allows the player to push around physics-objects in the world aswell as control windmills and wind powered switches

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class WindBox : MonoBehaviour {

    Vector3 mouseDownPos;
    Vector3 findMousePos;
    Vector3 movingBox;
    GodCameraMove god;

    bool townWind;

	PlayerController playerController;

	AudioSource windAS;
	
	GameObject audioFollowCamera;

	GodPowerController godController;

    //Initialize components

	void Start () {
        god = FindObjectOfType();
		playerController = FindObjectOfType ();
		godController = GameObject.FindGameObjectWithTag ("godpower_controller").GetComponent ();

		audioFollowCamera = new GameObject ();
		audioFollowCamera.name = "WindAudioObject";
		
		windAS = audioFollowCamera.AddComponent ();
		windAS.clip = godController.windSound;
		windAS.loop = true;
	}
	
	
	void Update () {

		audioFollowCamera.transform.position = god.transform.position;

        //While button is held down, follows mouse

        if(Input.GetKey(playerController.select)){
            SetDelayedRaycastHitPosition(true);

            movingBox = mouseDownPos - findMousePos;

            findMousePos = mouseDownPos;
            //Enables particle effect
			if(!windAS.isPlaying){
				windAS.volume = 1;
				windAS.Play();
			}
        }
        //Disables and removes the WindBox
		else if(Input.GetKeyUp(playerController.select)){
			mouseDownPos = Vector3.zero;
			StartCoroutine(FadeOutClip());
		}

        MoveBox();
	
	}

    //If the object in the windbox collider has the WindPower Interface, apply force in the direction the player drags the mouse
    void OnTriggerStay(Collider other)
    {
		if (!townWind)
        {
			WindPowerInteractable[] wPIComps = other.GetComponents ();
			foreach (WindPowerInteractable comp in wPIComps) {
				comp.OnWindStay(transform, Vector3.Magnitude(movingBox.normalized) * 10f);
			}
        }
    }
    
    //If the object in the windbox has the WindPower Interface, trigger the collision event for that object

    void OnTriggerEnter(Collider other)
    {
		WindPowerInteractable[] wPIComps = other.GetComponents ();
		foreach (WindPowerInteractable comp in wPIComps) {
			comp.OnWindHit();
		}		
    }

    //Failsafe for non-trigger colliders.

	void OnCollisionStay(Collision col)
	{
		if (!townWind)
		{
			WindPowerInteractable[] wPIComps = col.collider.GetComponents ();
			foreach (WindPowerInteractable comp in wPIComps) {
				comp.OnWindStay(transform, Vector3.Magnitude(movingBox.normalized));
			}
		}
	}

    //Failsafe for non-trigger colliders.

	void OnCollisionEnter(Collision col)
	{
		WindPowerInteractable[] wPIComps = col.collider.GetComponents ();
		foreach (WindPowerInteractable comp in wPIComps) {
			comp.OnWindHit();
		}		
	}

    //Raycast to mouse position

    public void SetDelayedRaycastHitPosition(bool upOrDown)
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;

        if (god.DeactivateLayerRaycast(ray, out hit))
        {
            if (god.TerrainLayerRaycast(ray, out hit))
            {
                if (upOrDown)
                {
                    townWind = false;
                    mouseDownPos = hit.point;
                }
            }
        }
        else
        {
            if (upOrDown)
            {
                townWind = true;
                mouseDownPos = hit.point;
            }
        }


        
    }

    //Function for rotating and moving the box on update while button is held down

    void MoveBox()
    {
        transform.position = mouseDownPos;
		if(movingBox != Vector3.zero)
        	transform.rotation = Quaternion.LookRotation(movingBox);
    }

    //Fade out delay for wind audio

	IEnumerator FadeOutClip(){
		while (windAS.volume > 0) {
			windAS.volume -= Time.deltaTime;

			if(windAS.volume <= 0){
				windAS.Stop();
			}

			yield return new WaitForSeconds(0.05f);
		}
	}
    
}

SpawnMeteor allows the player to shoot meteors at enemies and destructible objects.

MeteorTrajectory controls the direction the meteor travels in relation to the cameras current position so it never spawns in view.

using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
using DG.Tweening;

public class SpawnMeteor : MonoBehaviour {
	
	public GameObject meteorPrefab;
    GodCameraMove god;
    GodPowerController godController;
	PlayerController playerController;
    bool cooldown = false;

    //Initializes components
    void Start()
    {
        godController = GameObject.FindGameObjectWithTag("godpower_controller").GetComponent();
        god = GameObject.FindGameObjectWithTag("MainCamera").GetComponent();
        playerController = GameObject.FindGameObjectWithTag("player_controller").GetComponent();
    }
    //Check if the player presses a button. If true, tries to raycast.
	void Update () {
		if (Input.GetKeyDown(playerController.select) && godController.godPowers == GodPowerController.GodPowers.Meteor && cooldown == false) {
            if (EventSystem.current.IsPointerOverGameObject()) {
                return;
            }
            cooldown = true;
			Raycast ();
            StartCoroutine(Cooldown());
		}
	}
    //If raycast returns true, spawns meteor
	void Raycast(){
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;
        Vector3 spawnPoint = new Vector3(Camera.main.transform.position.x + 20, Camera.main.transform.position.y + 2, Camera.main.transform.position.z) + Random.insideUnitSphere * 5;
        if (god.DeactivateLayerRaycast(ray, out hit))
        {
            if (god.TerrainLayerRaycast(ray, out hit) && godController.godPowers == GodPowerController.GodPowers.Meteor)
            {
                GameObject spawnedMeteor = (GameObject)Instantiate(meteorPrefab, spawnPoint, Camera.main.transform.rotation);
                spawnedMeteor.GetComponent().GetTarget(hit.point);
            }
        }
	}
    //Cooldown couroutine
    IEnumerator Cooldown()
    {
        yield return new WaitForSeconds(2f);
        cooldown = false;
    }

}
using UnityEngine;
using System.Collections;
using DG.Tweening;

public class MeteorTrajectory : MonoBehaviour {

    public Vector3 targetLocation;
       
    
    float speed = 0.05f;
    

    Vector3 startLocation;
    float factor;

    //Initialize components and variables
    void Start()
    {
        startLocation = transform.position;
        transform.DOMove(targetLocation, 1.5f);
    }

    void Update()
    {
        if(targetLocation != null){
            factor += Time.deltaTime;
        
        
            }
    }
    //Function called by SpawnMeteor script to set the target
    public void GetTarget(Vector3 target)
    {
        targetLocation = target;
    }
}

© 2015 Kasper Holmberg

contact@kasperholmberg.com

(+46)76-8670450