Origo!

Jigoku - Game Designer & Unity Developer

Great gameplay makes use of every aspect of the game in ways sensible to the lore.

Stefan Alfredsson

+46 70 754 8228

LinkedIn Profile

stefan@jigoku.se

 

Title: Origo!

Genre: Competitive Build-n-Bash

Engine: Unity 4

Platform: PC

 

What it is:

Origo! is a two-player, hotseat, construction game based on a concept from FatShark. The goal of the game is to score higher than your opponent. Score is gained by firing assorted colored objects a gridded sphere, the Origo. Objects will stick to the Origo and each other and create combos if the objects are of the same color, giving the player more points. Bombs and magnets can also be fired to blow objects away from each other, keeping large combos from your opponent.

 

How it came to be:

  • The goal of the project was to create a Build-n-Bash, hotseat game based on the concept received from FatShark.
  • Development time was seven weeks.
  • Seven students were involved: three designers and four artists. No programmers.
  • With it's unique game mechanics the jury from FatShark said they had a lot of fun playing Origo! but that they were not quite sure why they enjoyed flinging pencils and flashlights at the Origo as much as they did.

 

My responsibilities:

  • Game Design
  • Gameplay Design
  • Scripting (C#)
  • Particles (Shuriken) & VFX
  • Optimization

 

Gameplay Video

Screenshots

Basic Game-Mechanics

 

Build anything

During each turn the current player is given 30 randomly selected projectiles. Most of these are various objects in one of three different colors (red, yellow and blue). These will attach to the Origo and other objects attached to the Origo. This allows the player to build long chains of objects.

 

Color combos

By stacking objects of the same color on top of each other the current player will build a color combo. Combos give exponentially more points than single objects and are key to achieving a high score.

 

Destruction

There are also two special projectiles, bombs and magnets. Both of these allows the player to destroy combos in order to keep them from their opponent. Bombs explode upon collision and push nearby objects away. Magnets pull along objects they pass by and explode after a few second. After being pushed away objects will get pulled back towards the Origo, allowing for even more points when they attach once again.

 

Scripting

 

Projectile gravity

This script simulates gravity between a projectile and all objects in an array. In OriGo! we only use a single object instead of multiple gravitational bodies but the system has support for more than a single gravitational object.

 

The following video shows how the game could look with four gravitational bodies, applying appropriate forces on each projectile in real time. In this video the projectiles are affected by gravity right away when launched. In the normal game the projectiles are only affected by gravity after being detached from the Origo.

Exported from Notepad++
using UnityEngine; using System.Collections; /* Creator: Stefan Alfredsson * Author: Stefan Alfredsson */ /* * This script is used to simulate gravity between the projectile and the objects placed in the m_Planets array. * */ public class ProjectileGravity : MonoBehaviour { private Transform m_Transform; private GameObject m_GameObject; private float gravConst = ( 6.67f * Mathf.Pow ( 10f, -11f ) ); // Universal Gravitational constant G = ( 6.67f * Mathf.Pow ( 10f, -11f ) ) private GameObject[] m_Planets; private float[] m_PlanetMassArray; private float[] m_PlanetDeadZoneArray; private bool m_ProjectileOutsideDeadZone = true; private PlanetArrayScript m_PlanetArrayScript; private PlanetProperties m_PlanetPropertiesScript; private ProjectileProperties m_ProjPropertiesScript; private float m_forceClamp = 1000f; void Start () { m_Transform = transform; m_GameObject = gameObject; m_PlanetArrayScript = (PlanetArrayScript)FindObjectOfType( typeof ( PlanetArrayScript ) ); m_Planets = m_PlanetArrayScript.m_PlanetArray; m_PlanetMassArray = new float [ m_Planets.Length ]; m_PlanetDeadZoneArray = new float [ m_Planets.Length ]; m_ProjPropertiesScript = m_GameObject.GetComponent < ProjectileProperties > (); for ( int i = 0; i < m_Planets.Length; ++i ) { m_PlanetPropertiesScript = m_Planets[ i ].GetComponent< PlanetProperties >(); m_PlanetMassArray[ i ] = m_PlanetPropertiesScript.PlanetMass; m_PlanetDeadZoneArray[ i ] = m_PlanetPropertiesScript.PlanetDeadZone; } } void FixedUpdate () { /* * If the projectile isn't connected to a planet. * Loop through the objects in m_Planets and check if the projectile is outside the deadzone. * If the projectile is outside of the deadzone it is pulled by all objects in m_Planets, using the GravPull-method. * */ if ( !m_ProjPropertiesScript.connectedToPlanet ) { for ( int currentPlanet = 0; currentPlanet < m_Planets.Length; ++currentPlanet ) { float planetDistance = Vector3.Distance( m_Planets [ currentPlanet ].transform.position, m_Transform.position ); if ( planetDistance < m_PlanetDeadZoneArray [ currentPlanet ] ) { m_ProjectileOutsideDeadZone = false; break; } else { m_ProjectileOutsideDeadZone = true; } } if ( m_ProjectileOutsideDeadZone ) { for ( int currentPlanet = 0; currentPlanet < m_Planets.Length; ++currentPlanet ) { GravPull ( m_GameObject, m_Planets[ currentPlanet ].transform.gameObject, m_PlanetMassArray[ currentPlanet ], m_ProjPropertiesScript.rigidBodyMass ); } } } } /* * Applies a force on _Satellite towards _Planet. * Uses the mass of both objects to calculate the force (_SatelliteMass and _PlanetMass). * Clamps the force applied in order to avoid extreme acceleration if both objects are very close to eachother. * */ private void GravPull ( GameObject _Satellite, GameObject _Planet, float _SatelliteMass, float _PlanetMass ) { if ( _Satellite.GetComponent < Rigidbody > () ) { float distance = Vector3.Distance( _Planet.transform.position, _Satellite.transform.position ); float gravForce = gravConst * ( ( _PlanetMass * _SatelliteMass ) / Mathf.Pow ( distance, 2f ) ); Vector3 angle = ( _Planet.transform.position - _Satellite.transform.position ).normalized ; Vector3 endForce = ( angle * gravForce ); //Clamps maximum force Mathf.Clamp ( endForce.x, -m_forceClamp, m_forceClamp ); Mathf.Clamp ( endForce.y, -m_forceClamp, m_forceClamp ); Mathf.Clamp ( endForce.z, -m_forceClamp, m_forceClamp ); _Satellite.rigidbody.AddForce ( angle * gravForce ); } } /* * Applies a force on _Satellite towards _Planet using the rigidBody-masses of both objects. * */ private void GravPull ( GameObject _Satellite, GameObject _Planet) { float distance = Vector3.Distance( _Planet.transform.position, _Satellite.transform.position ); float gravForce = gravConst * ( ( _Planet.rigidbody.mass * _Satellite.rigidbody.mass ) / Mathf.Pow ( distance, 2f ) ); Vector3 angle = ( _Planet.transform.position - _Satellite.transform.position ).normalized ; _Satellite.rigidbody.AddForce ( angle * gravForce ); } }

Magnetic projectile

This script is used on the magnetic projectiles to detach and attract other projectiles. It mostly works like the projectile gravity to the Origo, only instead of an array of gravitational bodies each magnetic projectile works independently.

Exported from Notepad++
using UnityEngine; using System.Collections; using System.Collections.Generic; /* Creator: Stefan Alfredsson * Author: Stefan Alfredsson */ /* * This script attracts nearby projectiles, like a magnet. * */ public class MagneticMissileAttractor : MonoBehaviour { private Transform m_Transform; private Vector3 m_Position; private MagneticMissileProperties m_MagnetProperties; private MagneticMissileCollision m_MagnetCollision; private float m_SensorRadius; private Vector3 m_ProjectileDirection; // Direction from bomb towards projectile private float m_ProjectileDistance; // Distance from bomb to projectile private float m_MagneticForce; // Magnetic force at center of missile. Gotten from MagneticMissileProperties. private Vector3 m_ResultForce; // Resulting force which is added on nearby projectiles during Explode method. private List < Collider > m_MagnetizedProjectiles = new List < Collider > (); private GameObject m_TempCollider; // Used to cache currentProjectile in Explode method. void Start () { m_Transform = transform; m_Position = m_Transform.position; m_MagnetProperties = GetComponent < MagneticMissileProperties > (); m_MagnetCollision = GetComponent < MagneticMissileCollision > (); m_MagneticForce = m_MagnetProperties.magneticForce; } /* * See if any new colliders are nearby. * If any other colliders are, or have been, near the magnetic missile: attract these objects. * */ void Update () { m_MagnetizedProjectiles = m_MagnetCollision.GetMagnetizedProjectiles ( m_MagnetizedProjectiles ); if ( m_MagnetizedProjectiles.Count > 1 ) { AttractNearbyProjectiles ( m_MagnetizedProjectiles ); } } /* * Method for attracting objects. * Only attracts existing objects in _NearbyProjectiles that are not attached to the magnetic missile. * */ public void AttractNearbyProjectiles ( List < Collider > _NearbyProjectiles ) { m_Position = m_Transform.position; if ( _NearbyProjectiles == null ) { return; } for ( int currentProjectile = 0; currentProjectile < _NearbyProjectiles.Count; ++currentProjectile) { if ( _NearbyProjectiles [ currentProjectile ] != null ) { m_TempCollider = _NearbyProjectiles [ currentProjectile ].gameObject; /* * If the hit object is a projectile and not a child of magnetMissile * Moved block below into above if-statement * */ if ( m_TempCollider.transform.parent.GetComponent < ProjectileProperties > () && !m_TempCollider.transform.parent.transform.IsChildOf ( m_Transform ) ) { if ( !m_TempCollider.transform.parent.GetComponent < Rigidbody > () && m_TempCollider.transform.parent.GetComponent < ProjectileProperties > () ) { m_TempCollider.transform.parent.GetComponent < ProjectileCollision > ().DisconnectFromPlanet (); } m_ProjectileDirection = ( m_Position - m_TempCollider.transform.position ).normalized; m_ProjectileDistance = Vector3.Distance ( m_Position, m_TempCollider.transform.position ); m_ResultForce = ( m_MagneticForce * m_ProjectileDirection ) * m_ProjectileDistance; m_TempCollider.transform.parent.rigidbody.AddForce ( m_ResultForce ); } } } } }

Combo detection

This script detects the size of a combo by finding the top parent in the current hierarchy of game objects. When the parent no longer matches the starting projectile's color the script goes through all of the top parent's children. For each child of a matching color the child's children are checked and 1 is added to a counter. After all children have been checked the counter's value is returned.

Exported from Notepad++
using UnityEngine; using System.Collections; using System.Collections.Generic; /* Creator: Stefan Alfredsson * Author: Stefan Alfredsson */ /* * This script checks if a projectile is attached to other projectiles of it's own color. If it is the parent-projectile then checks it's parent. * This continues until the parent no longer is a projectile of the same color. * At this point all direct children's color is checked. If the color matches then the next level of children are checked. * For every color match the matching projectiles is added to a list. This continues until the child no longer is a projectile of the same color. * The length of the list is then returned, giving the size of the combo. * */ public class ProjectileComboScript : MonoBehaviour { private Transform m_Transform; private Transform m_Parent; private Transform m_TopParent; private ProjectileColor m_ProjectileColorScript; private int m_SelfColorIndex; private int m_ComboCounter = 0; private int m_TempInt = 0; private List < Transform > m_ComboProjectilesList = new List < Transform > (); // Script for flashing color of projectile. private ProjectileComboFlash m_ProjComboFlashScript; void Start () { m_Transform = transform; m_ProjectileColorScript = GetComponent < ProjectileColor > (); m_SelfColorIndex = m_ProjectileColorScript.projectileColorIndex; m_ProjComboFlashScript = GetComponent < ProjectileComboFlash > (); } /* * Clears projectile list for the combo. * Finds all child-projectiles with matching colors. * Returns the number of projectiles in the combo. * */ private int CheckParentColor ( Transform _TopParent ) { m_ComboProjectilesList = ResetChildList ( m_ComboProjectilesList ); m_TempInt = CheckChildColor ( m_ComboProjectilesList ); return m_TempInt; } /* * Plays flash effect for child projectile and adds it to projectile list. * Checks for more projectiles with mathcing colors further down the hierarchy. * Returns the number of projectiles in the combo. * */ private int CheckChildColor ( List < Transform > _ComboProjectilesList ) { m_ProjComboFlashScript.Flash (); _ComboProjectilesList.Add ( m_Transform ); // Iterate through all children for this projectile. foreach ( Transform child in m_Transform ) { // If child is another projectile. if ( child.GetComponent < ProjectileProperties > () ) { // If child projectile has the same color as this projectile: have the child run this method as well. if ( child.GetComponent < ProjectileColor > ().projectileColorIndex == m_SelfColorIndex ) { child.GetComponent < ProjectileComboScript > ().CheckChildColor ( _ComboProjectilesList ) ; } } } return _ComboProjectilesList.Count; } /* * Clears the projectile list for the combo and returns it. * */ private List < Transform > ResetChildList ( List < Transform > _ComboProjectileList ) { _ComboProjectileList.Clear (); return _ComboProjectileList; } /* * Checks for the Projectile furthest up the hierarchy with the same color as the current projectile (_TempTransform). * */ private Transform GetTopParent ( Transform _TempTransform ) { int _SelfColorIndex = _TempTransform.GetComponent < ProjectileColor > ().projectileColorIndex; m_Parent = _TempTransform.parent; Transform _TopParent = _TempTransform; // If attached to another projectile. if ( m_Parent.GetComponent < ProjectileProperties > () ) { // If parent-projectile has the same color as this projectile: the parent-projectile is the new top-projectile. if ( m_Parent.GetComponent < ProjectileColor > ().projectileColorIndex == _SelfColorIndex ) { _TopParent = m_Parent.GetComponent < ProjectileComboScript > ().GetTopParent ( m_Parent ); } } return _TopParent; } /* * Called by projectile upon collision. * Returns size of the combo. * */ public int GetComboCounter () { m_TopParent = GetTopParent ( m_Transform ); m_ComboCounter = m_TopParent.GetComponent < ProjectileComboScript > ().CheckParentColor ( m_TopParent ); return m_ComboCounter; } }

Stefan Alfredsson - Designer & Unity Developer - E-mail: stefan@jigoku.se - Mobile: +46 70 754 8228