Thursday, December 28, 2017

Particularly Wavy Update

hey all,

I have not gotten nearly as much work done as I wanted to this week. I've had more or less full-time work hours at my (kind of) paying job, which has greatly reduced the hours I can put into the game.

But what I have got done is this: I now have 41 puzzles complete and playable. The first 30 deal with reflection, while the next 20 (once completed) will deal with refraction. In that vein, I have removed a vicious set of bugs due to some recursive functions that I was using to deal with interior reflection inside a prism. These bugs at first caused Unity's garbage collector system to go haywire, with lots of errors spamming the console about "78 bytes allocated at " some random location, and after getting those to stop, I ran into a bug that caused Unity to simply crash. So, I was forced to greatly simplify the calculation of these internal reflections, since they could cause the light to bounce around inside the prism 5, 10, 20, or even 30 times before exiting, and Unity would have to deal with potentially 100's of game objects being created and destroyed each frame if the player was moving or rotating the prism.

I have also submitted the game to BitSummit 2018, which due to my excitement about going to GDC earlier this year I forgot to do for BitSummit 2017. I'll be updating everything I can about the game in terms of publicity before we enter 2018: the latest builds for 32-bit and 64-bit will be uploaded to BitSummit and to the game's page on itch.io, there will be a devblog on itch.io about the updates, and Twitter shall crash due to all the views and retweets about my amazing game, and as Wayne Campbell would say, "Cha, and monkeys might fly out of my butt!"




Well, in any case, I have actually updated my BitSummit submission, the itch.io version, posted the game to FaceBook and to Twitter, and YouTube. And I have spent more time than I care to admit tracking down database errors, problems loading and saving XML during runtime after compiling and building the game, making sure that Unity is properly saving the data about each object that I place and configure while I am designing the levels (which since I am using a number of custom inspector scripts, is actually quite difficult or at least annoying), and a host of other obstacles.

That is all the work I can put in on the game in 2017 and maintain my sanity.My wife and I will be out of Japan on a much deserved vacation until January 8th, 2018, so I will not be programming or designing anything. Although, like any other time, I will probably come up with a few ideas for prototypes, new games, and things while I am brushing my teeth, taking a shower, or staring into space or enjoying a view on our trip.

Peace and love to all my friends and family. If you had a great year in 2017, I hope 2018 knocks your socks off, and if 2017 put you through the wringer, I hope you put 2018 through it instead.

Thursday, December 21, 2017

Latest Update For Particularly Wavy

hey all,

It's been a while since my last post here. Part of the delay has been due to me catching a cold, and the rest is just that I've been too busy coding to actually write or talk about what I've been coding. One thing that is shown in the video is really smooth detection of hits: in the very first version of the game, I was performing raycasts every frame and based on those results, deciding if I needed to recalculate the lights, which as you might imagine leads to lots of frames where I perform the raycast and decide to do nothing.

In the latest version, I have delegates and events on every object that moves, rotates, or changes size, and when they do one of those things, the laser receives a message letting it know that something has moved, so it can then update the light positions and angles.



The problem was that when the light hit something, I was starting certain coroutines that would spam a message to the hit object once every frame, and in the code for the hit object I was running a bunch of checks inside the Update() function, which is run once every frame. Why would I do something stupid like that? Well, say you perform your calculation and you determine that light ray A is now hitting object O. You set the hit object variable of light ray A to object O and go on calculating what other results fall out from that. But what if on the previous frame light ray A was hitting object T, and object T is the target? In my game, I have a flag set inside the Target.cs script so that it knows when it is hit and what it is hit by. How do I tell the Target that it is no longer hit? That is the reason for the coroutines and Update() code: if the Target stopped receiving the message of being hit from the coroutine, which is stopped whenever I update the light anyway, then the Target can act correctly.

If you look at the code below, however, you will notice a different technique. I have a private variable called hitObject, and I have a public accessor for this called HitObject. Inside the setter, I run comparisons between the incoming value and the previous value of hitObject. Based on those, I send messages using Unity's built-in messaging system. These messages let the object know that it is no longer hit by or has just been hit by a particular light ray, as the case may be. Problem solved: no messy coroutines that need to be started or stopped, no code running every frame inside of Update() (OK, OK, no code besides the shader material updates) and using up CPU time. When something changes, the messages are sent and if nothing has changed, then nothing needs to be updated or checked.


using System.Collections.Generic;
using UnityEngine;

//[RequireComponent(typeof(CapsuleCollider))]
public class RayNode : MonoBehaviour
{
 private GameObject hitObject;
 public List < RayNode > children;
 public LineRenderer rayLR;
 public int depth;
 public bool influencedByBlackHole;
 public bool intensified;
 public GameObject metaball;

 public GameObject HitObject
 {
  get { return hitObject; }
  set
  {
   if ((value == null && hitObject != null ))
   {
    hitObject.SendMessage("OnLightExit", gameObject, 
SendMessageOptions.DontRequireReceiver);
    hitObject = null;
   }
   else if ((value != null && hitObject != value && hitObject != null ))
   {
    hitObject.SendMessage("OnLightExit", gameObject, 
SendMessageOptions.DontRequireReceiver);
    hitObject = value;
    hitObject.SendMessage("OnLightEnter", gameObject, 
SendMessageOptions.DontRequireReceiver);
   }
   else if ((hitObject == null && value != null))
   {
    hitObject = value;
    hitObject.SendMessage("OnLightEnter", gameObject,
SendMessageOptions.DontRequireReceiver);
   }
   else
   {
    hitObject = value;
   }
  }
 }

 float offset;

 private void Start()
 {
 }

 private void Update()
 {
  offset -= Time.deltaTime * 2f;
  if (offset < -.5f)
  {
   offset += .5f;
  }
  rayLR.materials[1].SetTextureOffset("_MainTex", 
new Vector2(offset / 4f, 0));
  rayLR.materials[2].SetTextureOffset("_MainTex", 
new Vector2(offset, 0));

 }


 public RayNode()
 {
  children = new List < RayNode > ();
 }

 public RayNode(GameObject RO)
 {
  hitObject = RO;
  children = new List < RayNode > ();
 }

 public void PruneSubTree(int childIndex)
 {
  //validate index
  if (childIndex > -1 && childIndex < children.Count)
  {
   RayNode toPrune = children[childIndex];
   //set the hitObject to null
   if (toPrune != null)
   {
    toPrune.HitObject = null;
   }
   

   List < RayNode > ch = new List < RayNode > ();
   for (int i = 0; i < toPrune.children.Count; i++)
   {
    ch.Add(toPrune.children[i]);
   }

   //reverse order is important in order to prevent skipping errors
   for (int i = ch.Count - 1; i > -1; i--)
   {
    toPrune.PruneSubTree(i);
   }

   if (toPrune != null)
   {
    Destroy(toPrune.gameObject, 0.1f);
   }
   
   children.Remove(toPrune);
  }
 }

 public List GetChildren()
 {
  return children;
 }

 public void PruneWholeTree()
 {
  if (children.Count > 0)
  {
   List < RayNode > ch = new List < RayNode > ();
   for (int i = 0; i < children.Count; i++)
   {
    ch.Add(children[i]);
   }

   //reverse order is important in order to prevent skipping errors
   for (int i = ch.Count-1; i > -1; i--)
   {
    PruneSubTree(i);
   }
  }
 }

 public void UpdateChildren(Vector3 startPosition)
 {
  for (int i = 0; i < children.Count; i++)
  {
   children[i].gameObject.transform.position = startPosition;
   children[i].rayLR.SetPosition(0, startPosition);
  }
 }

 public void HandleMetaBall()
 {
  metaball.transform.position = (rayLR.GetPosition(0) + rayLR.GetPosition(1)) / 2f;
  Vector3 dir = rayLR.GetPosition(1) - rayLR.GetPosition(0);
  metaball.transform.rotation = Quaternion.AngleAxis(
Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg,
 Vector3.forward);
  float scaleFactor = 0;
  
  //set scaling factor in case of low distances
  if (Vector3.Distance(rayLR.GetPosition(1), rayLR.GetPosition(0)) < 5f)
  {
   scaleFactor = 0.2f;
  }

  metaball.transform.localScale = new Vector3((scaleFactor + 1.1f) * 
Vector3.Distance(rayLR.GetPosition(1), rayLR.GetPosition(0)), 2f, 1f);
 }
}


This simple fix solved several other problems as well. The targets need to keep track of what light is hitting them, and previously each light ray would send a message letting the target know this. I would have to clear the list at the beginning of a light update cycle, then add each light ray, and at the end of a frame, I would have to perform a check to see if the target's condition had been met. The exact timing of that check is important, because performing the check in-between light rays being added would lead to false positives: the target needs to be hit by red light and only red light, for example, and after one check it is being hit by red light, so the condition is marked as being satisfied. But then orange light and yellow light send their messages to the target and now the condition is not satisfied. But the message has already been sent to the game manager, which now congratulates the player on solving the puzzle even though they have not solved the puzzle. You get the idea. Since I was performing these checks every time an object was moved, that just increased the chance that one poorly timed message would screw the whole thing up. Now, I only perform these checks when a light ray first hits the target and when it stops hitting the target, greatly reducing the chance of a false positive.

One final problem that has only come to light recently is null references. Normally, these would be huge signal fires that something has broken somewhere, but these only started showing up when I changed some of my laser code to be updated just as the program stops or shuts down. When I did that, suddenly the console was getting clogged by null reference errors. Luckily, the fix was really simple: inside the PruneSubTree function, I now check if the child to delete is null and if it is not, then I delete it. That's it: no more errors.

I've been spending so much time going through and fixing these problems, updating the chargeable objects and activate-able objects, etc, that I still have not gotten around to finishing the code for my heat-able objects. But I hope to have that finished before New Years.