Unity: Android Native Crash – [Solved] MP4 Audio Encoding Problem

I’ve been in Beta Testing for a new game I’m about to release on the Google Play Store (the game is called Endless Elevator). I kept having Native Crashes on specific Android platforms in all my builds in the Pre-Launch Reports. Native Crashes can be terrible to work through if you get unlucky so I was a bit worried and figured I’d just have to leave it like it was and release with errors! But being a bit stubborn I threw a few days into sorting through it and am very glad I did. Working through the problem highlighted some things I didn’t know about Android Video support and was an interesting exercise in troubleshooting. So here is the method I followed and the resolution to the problem.

In each case it was always the armeabi-v7a package that was causing the issues. (I split my build into two APK’s for arm64 and armeabi to make it a smaller installation size – I haven’t gone the android bundle path yet).

These are some of my base Beta builds and in most cases there were 4 errors relating to specific platforms.

The Pre-Launch tests are run on a variety of Android platforms but usually they will include these four below in some form or other and my build kept crashing with a Native Error on each of them.

The usual suspects

When I looked at each of them in turn and played the video of the interactive session the fail point always seemed to be about the time when I had a full screen projected video playing or about to play. The video is used as an introduction and tutorial to the game so it was pretty important for me to get it working.

The drill down screen of the crash report where you can see the video of the session and get access to the logs.

I downloaded all the Logcat’s from the console above and looked for any errors or crash reports.

In each case I found this line (which was a bit of a dead giveaway):

——— beginning of crash

A half dozen lines above the likely culprit was writ large:

07-23 04:00:47.862: W/MediaAnalyticsItem(9345): Unable to record: (codec:0:-1:-11:0:3:android.media.mediacodec.mime=audio/ac3:android.media.mediacodec.mode=audio:android.media.mediacodec.encoder=0:) [forcenew=0]
07-23 04:00:47.890: W/Unity(9345): AndroidVideoMedia: Could not create decoder for mime type audio/ac3.
07-23 04:00:47.890: W/Unity(9345): (Filename: Line: 2177)
07-23 04:00:47.906: I/Robo(9288): No foreign elements detected, falling back to original ScreenState.
07-23 04:00:47.910: I/Robo-HyperMultiGraph(9288): New Screen: Optional.of(ScreenNode {Id=5, PackageName=com.ZuluOneZero.EndlessElevator, ActivityName=Optional.of(com.unity3d.player.UnityPlayerActivity)})
07-23 04:00:47.913: E/Unity(9345): Could not allocate memory: System out of memory!
07-23 04:00:47.913: E/Unity(9345): Trying to allocate: 4294705156B with 16 alignment. MemoryLabel: Audio
07-23 04:00:47.913: E/Unity(9345): Allocation happened at: Line:70 in
07-23 04:00:47.913: E/Unity(9345): Memory overview

A bit of googling about led me to believe that as per the error message above the audio codec used in the video was a problem. The AC3 codec is an Audio format that’s used in my MP4 Video. I’d never given it much thought but this format is not supported across all the Android platforms (one of the problems of Android development is that there is so many different platforms out there).

The Video Editing Software that I normally use is called OpenShotVideo. It’s fantastically good for the price (free) and is easy to use and powerful enough for my meagre needs. Turns out the default audio codec used is AC3 (there is probably a way to modify this with OpenShotVideo but I wasn’t in the mood to troubleshoot someone else’s software). I really hadn’t given the audio codec part of the MP4 a second thought.

This is the Export Panel from OpenShotVideo where I confirmed that the Codec was indeed ac3.

While I was doing all this work and after I worked out that the audio codec in the Video was the problem I had a look at the video settings in Unity. I found that there was already a built in transcoder that I’d never noticed right there in the Unity Video Asset Import screen.

Transcode !

That’s pretty cool! Unity has already solved all my problems before I even knew I had them. So I hit the Transcode tick box and waited for twenty minutes while it went to work transcoding. That wait time should have been a bit of a warning. I did the build and uploaded the new apks to the Google Developer Console but while doing that I found that my build size had jumped almost 17 MB!

This was my size before the transcoding:

And afterwards:

A quick look at the Editor.Log confirmed that the transcoding process had made my lovely low quality 7 MB Movie over 20 MB:

Used Assets and files from the Resources folder, sorted by uncompressed size:
22.1 mb 6.8% Assets/Art/IntroMovePlusFoyerClippedSlowLow.mp4

To fix this I downloaded the HandBrake Open Source Video Transcoder and transcoded my original video asset using the AAC Codec for Audio.

The HandBrake Tool

After importing this into my project and rebuilding again I was left with a similar package size and no Native Crashes. Hooray. I’m going to release this Beta Build to Production soon so getting over this little hurdle feels like a huge V for Victory. Huzzah.

Unity – Unable to update the SDK. Please run the SDK Manager manually.

I had this problem during a build this week and figured I’d better document it 1. In case anyone else needs to do it and 2. So I don’t forget it myself for next time.

I’m running Unity 2019.4.1f1 and doing the final few Beta builds for the game I’m just about to release called Endless Elevator. (If you want a quick peek before it’s released the beta has a few more days to run). I uploaded a new build to the Google Play Developer Console and noticed a new message saying something along the lines that API 20 Support was going to be discontinued soon and to check my API minimum levels. I did this and also noticed that I hadn’t updated my game’s API support for the latest releases (API 29 and 30) .

I tried to update it from within the Player Settings of Unity:

This is where I encountered the problem of not being able to run the SDK update from within Unity (you can see API 29 & 30 are displayed differently)

… and after some time got a pop up message:

“Unable to update the SDK. Please run the SDK Manager manually to make sure you have the latest set of tools and the required platforms installed.”

I had not idea what it meant by “manually” so I went into my local copy of the Android Development Studio and performed a full update of all the APIs’ and Tool packs. This didn’t help. Probably because I’d forgotten that the Unity version I was running had it’s own set of API’s in another location.

This is where I found it: C:\Program Files\Unity\Hub\Editor\2019.4.1f1\ Editor\Data\PlaybackEngines\AndroidPlayer\SDK\tools\bin\

This is how I ran it manually from the Windows Command Prompt (cmd.exe):

“C:\Program Files\Unity\Hub\Editor\2019.4.1f1\Editor\ Data\PlaybackEngines\ AndroidPlayer\SDK\tools\bin\sdkmanager.bat” “platforms;android-29”

and

“C:\Program Files\Unity\Hub\Editor\2019.4.1f1\Editor\ Data\PlaybackEngines\AndroidPlayer\SDK\tools\bin\sdkmanager.bat” “platforms;android-30”

First time I ran this I had to accept the licence agreement (Y|n) from the command prompt and it seems that this was the problem. I’m guessing that the internal Unity update function didn’t allow for the licence prompt and was timing out.

I did some googling around and found a few examples of this issue – one person said they had to create a repositories.cfg file in their C:\<users>\.android folder (I already had one so not sure if this step is required).

Anyway fun days and looking forward to releasing the game later next week.

Endless Elevator – Last Week of Beta Testing

Hi Harmony here,

I’d like to thank the wonderful Beta Testers who have signed up for our Open Beta of Endless Elevator over the last month. I was really surprised how many people responded to our call and very grateful to all those who provided feedback.

The Open Beta is still running for another week so there is still time to try the pre-release game: https://play.google.com/apps/testing/com.ZuluOneZero.EndlessElevator

Then head over to our Beta Landing Page if you want to give us some feedback.

http://www.zuluonezero.net/endless-elevator-beta/

Your feedback helps us make a better game – thank you!

One feature of our Beta Testing was opening multiple channels to provide users to get back to us. Not only did we use the Google Play Beta Feedback and Stars system but we also took feedback on email and through social media. One new thing we did was to incorporate a chat channel on our Beta Landing Page where users could provide anonymous feedback directly on to our web site. I feel that the anonymity of this format was a really important source of quality comments for us.

Our Chat Page

Happy Playing – Harmony Out.

Endless Elevator – Early Access Beta

Endless Elevator went into Beta Testing on May 27, 2020, 8:10 PM.

Finally we made it to public testing! After a few months of keeping myself quiet and a seeming eternity of bug fixes I think I got a minimum viable product fit for your eyes, ears, and phone-ready fingers.

Please head over to our Beta Test Page and try it out. I’d love to get some feedback from other enthusiasts and developers.

Get it Here!

Chat With Us!

We have an anonymous Chat Channel right there on the page that you can trash talk into so go for broke! (You are also allowed to be supportive and kind as well if that is your nature).

🙂

The Beta Page has more information about the game as well links to articles written during development that explore the mechanics of the game.

The Coin Flip

Ah the coin flip! Simple easy and fun. The mechanism is a platformer mainstay. You run over a spinning coin (it glitters, it calls you) it pops into the air, and it’s yours!

The Endless Elevator Coin Flip !

This is how we do it…

There is a Rigidbody and Collider on both the coin and the player character. You can clearly see the spinning frame of the coin collider in the .gif above.

The collider acts as a trigger which is being listened for by our script which executes the “pop”.

The coin has a spinning script (and also a magnetic feature as a bonus for later).

The player has a couple of behaviours that handles the trigger and action.

The coin scripts – note the use of the slider to get the spinning speed just right.

Here we have the coin’s Rigidbody and Collider settings:

The Rigidbody isKinematic and the Collider is a Trigger

This is the script we use for spinning:

public class spinCoin : MonoBehaviour {

    [Range(0.0F, 500.0F)]
    public float speed;
	
	// Update is called once per frame
	void Update () {
        transform.Rotate(Vector3.up * speed * Time.deltaTime);
    }
}

Simple and sweet.

This is the function that that handles the collision and the pop into the air! (It’s part of our character behaviours).

    void OnTriggerEnter(Collider otherObj)
    {
        if (otherObj.name == "Coin(Clone)")
        {
            coins++;
            var coin_txt = coins.ToString();
            coinsText.text = "Coins: " + coin_txt;
            Rigidbody riji = otherObj.GetComponentInParent<Rigidbody>();
            riji.useGravity = true;
            riji.isKinematic = false;
            riji.AddForce(Vector3.up * 40f,  ForceMode.Impulse);
            Destroy(otherObj.gameObject, 0.4f);
        }
     }

First of all we are incrementing our coin total variables and screen display.

The mesh for the coin is part of a child component so we need to call the Rigidbody attached to the parent object.

We set gravity to true so that it falls back down after adding the force and set isKinematic to false so we can use it’s mass to fall.

After a very short flight we destroy it (0.4 seconds).

As an added bonus here is the other coin behaviour for when a magnet power up is used in the game.

    private void OnTriggerEnter(Collider col)
    {
        colName = col.gameObject.name;
        float step = speed * Time.deltaTime; // calculate distance to move

        if (colName == "chr_spy2Paintedv2")
        {
            transform.position = Vector3.MoveTowards(transform.position, col.gameObject.transform.position, step);
        }
    }

This is when we make the collider really big on the coin. If the player gets in range of the collider then the coin moves like a magnet towards him. Which is kinda fun when there is lots of coins around and you feel like a millionaire just by standing around.

Endless Unity Camera Tricks

In our game currently under development called Endless Elevator I decided to add a new feature of more depth.  The game is 2.5D and mostly sits in a very shallow Z axis, a limited X axis, and an endless Y axis. As the name suggests your character is inside a never ending building trying to kill or avoid the enemies by escaping up elevators and escalators. The mock-up of a level below in the Scene view of Unity gives you the picture.  

Endless Elevator Opening Level Spawn

The player view is a much smaller slice of this level…about this much:

Roughly how much the camera views

I had the idea that I wanted to extend this playing field into a deeper third dimension where the character could walk down a hallway,  away from the camera, and seemingly deeper into the building.  Instead of the camera following the character down the hall on the Z axis (as it follows him on the X and Y) I wanted to pan around the edge of the building and take up the character again on the new parallel so that it looks like the camera is turning Ninety degrees and that we are looking at a new side of the building.  

Have a look at the .gif below and I’ll try and explain that better. The top half of the image is the Scene view and the bottom half is what the camera (and the player) sees in the game.  In the top half  Scene view you can see my character in green and highlighted by the handler arrows. He scoots around a bit and then disappears down the hall. When he gets to a set depth it triggers the camera to move in the game window below and you will see the levels reconfigure into a new building face (note the elevators and escalators and doors will be in different positions).

On the bottom half of the .gif that shows what the player sees it looks like once the character disappears down the hall the camera pans right, looks at the edge wall of the building as it goes around the corner in a Ninety degree turn, and then follows the character on the new level again.

(Watch the top half for a bit then the bottom half)

You can see in the Scene view we are not really moving anything with the building. It just recreates the levels. The camera is doing all the work.  It’s not perfect yet and without any background around the building to relate the movement to it’s a bit hard to tell if we are turning Ninety or One Hundred and Eighty degrees on that camera flip but it’s getting there.

It took a while to work out how to do this and I tried several different methods but this is the basic logic of the camera move script that is attached to the character.

  1. The movement of the camera is triggered when the character moves past a certain point on the Z axis.
  2. Stop the regular character and camera movement functions by disabling that scripted behaviour on each object.
  3. I set up an empty game object called the CameraLookAtPoint that hovers at the end of the building on the far Right of the X axis.
  4. Pan the camera Right toward the CameraLookAtPoint.
  5. When the camera gets to within a certain distance to the LookAtPoint it starts to rotate towards it.
  6. The camera moves around the LookAtPoint so that it faces that end wall of the building as it turns.
  7. At this point while the only thing the camera can see is that blank edge wall of the building I destroy the old level we just walked out of down the hall and create a new randomly generated level.
  8. This is the great illusion! The camera is then moved instantly to the far Left of the building (the opposite end) and it appears as if we have just turned a corner.
  9. Lastly the camera picks up the character again and we hand control back to the normal camera and character movement scripts.
This is where the CameraLookAtPoint sits that the camera rotates around  Ninety degrees as it gets to the edge of the building.

I’ll post the whole script below with comments but I’ll walk through the interesting bits here. 

To start off with I needed to grab references to several external elements like the camera, the level instantiating script, the character controller script, and the characters Rigidbody. (I needed the rb because when the levels were destroyed and recreated gravity would take hold between them and the character would fall into the endless abyss!)

I had to generate a series of “if” conditionals and Boolean flags to control the movements of the camera. This was surprisingly hard to get right. It’s often not intuitive when you are in the Update function what the looping iterations will do with your code but this allowed me to slow things down and get control back. 

The calls to the instantiateScene1 script were needed to copy variables used there in the main flow of the game to track what level we were on and how high up the building we had climbed. That way I could decouple that mechanism from this one and happily destroy levels and recreate them without interrupting the flow of the rest of the game.

 


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpinLevelDownHall : MonoBehaviour {
public GameObject cameraLookAtPoint;
public bool triggered;
public bool cleanAndRebuild;
public bool moveRightEnd;
public bool moveRightEndBack;
public bool moveLeftEnd;
public bool movebacktoCharacter;
public int levelHolder;
public float cleanUpHeightHolder;
public bool firstRunHolder;
public Vector3 target_camera_Position;
private GameObject the_camera;
private RigidbodyCharacter move_script;
private CameraFollow cameraFollow_script;
private InstantiateScene1 instantiate_script;
private Rigidbody rb;
// Use this for initialization
void Start () {
the_camera = GameObject.FindGameObjectWithTag("MainCamera");
move_script = GetComponent();
cameraFollow_script = the_camera.GetComponent();
instantiate_script = GetComponent();
rb = GetComponent();
}
// Update is called once per frame
void Update () {
if (transform.position.z > 15)
{
triggered = true;
moveRightEnd = true;
cleanAndRebuild = false;
if (triggered)
{
// Stop the character and the camera moving
move_script.enabled = false;
cameraFollow_script.enabled = false;
if (moveRightEnd)
{
// Set the target camera position on the x axis at the far right of the building
Vector3 target_camera_Position = new Vector3(78f, the_camera.transform.position.y, the_camera.transform.position.z);
// Set the camera look at point on the y axis (so it's on the same level as the player)
cameraLookAtPoint.transform.position = new Vector3(cameraLookAtPoint.transform.position.x, transform.position.y + 4f, cameraLookAtPoint.transform.position.z);
// start moving the camera
the_camera.transform.position = Vector3.MoveTowards(the_camera.transform.position, target_camera_Position, 50 * Time.deltaTime);
// When you get close to the end start swinging the camera around to look at the wall
if (the_camera.transform.position.x > cameraLookAtPoint.transform.position.x - 10)
{                        the_camera.transform.LookAt(cameraLookAtPoint.transform);
}
// When you get really close to the first position move the camera beyond the wall to the side
if (the_camera.transform.position.x > target_camera_Position.x - 0.5f)
{
target_camera_Position = new Vector3(78f, the_camera.transform.position.y, 4f);  // 4f is perfect when the camera is at -90 deg
moveRightEnd = false;
moveRightEndBack = true;
cleanAndRebuild = true;
if (moveRightEndBack)
{
the_camera.transform.position = Vector3.MoveTowards(the_camera.transform.position, target_camera_Position, 50 * Time.deltaTime);
// When you get really REALLY close to the second position move the camera to the negative X Axis side
if (the_camera.transform.position.z > target_camera_Position.z - 0.2f)
{
moveLeftEnd = true;
if (moveLeftEnd)
{
target_camera_Position = new Vector3(-78f, the_camera.transform.position.y, 4f);  // The other side
the_camera.transform.position = target_camera_Position;  // snap move the camera
the_camera.transform.LookAt(cameraLookAtPoint.transform);
moveRightEndBack = false;
moveLeftEnd = false;
movebacktoCharacter = true;
}
}
}
}
}
if (cleanAndRebuild)
{
// Call cleanup on everything
rb.useGravity = false;  // so the character doesn't fall through the floor
//cleanUp;
cleanUpHeightHolder = instantiate_script.floorCntr;
cleanUpHeightHolder = cleanUpHeightHolder * 8;  // so it cleans up all the floors
firstRunHolder = instantiate_script.firstRun;
instantiate_script.cleanUp(cleanUpHeightHolder, firstRunHolder);  // cleanup height is usually two levels below the character - we are raising it to two levels above him
//then make three levels
levelHolder = Mathf.RoundToInt(instantiate_script.player_level);
instantiate_script.makeLevel(levelHolder);
instantiate_script.makeLevel(levelHolder + 8);
instantiate_script.makeLevel(levelHolder + 16);
cleanAndRebuild = false;
}                
triggered = false;  // makes it only run once
}
}
if (movebacktoCharacter)
{
// new position is back to the character
// Start breaking and making your new levels in here
// first move the character
transform.position = new Vector3(transform.position.x, transform.position.y, -4);
target_camera_Position = new Vector3(transform.position.x, transform.position.y + 4.9f, -20.51f);  // the starting camera position
the_camera.transform.position = Vector3.MoveTowards(the_camera.transform.position, target_camera_Position, 50 * Time.deltaTime);
the_camera.transform.LookAt(transform);
// Once again when you get close to your original camera position disable and enable normal camera tracking again
if (the_camera.transform.position.x > transform.position.x - 0.02f)
{
movebacktoCharacter = false;
rb.useGravity = true;
move_script.enabled = true;
cameraFollow_script.enabled = true;
}
}
}
}

It’s not the prettiest code, and I admit to hacking my way through it, but it works.  Maybe you have a better method for doing something similar – if you do please feel free to add a comment – I’d like to hear it.

Zulu Out.

Making a Custom Navigation Mesh for AI

Hi Xander here…

This week I decided to totally redo the way I have been handling character movement.

I used to have a free ranging character controller that basically moved in the direction your joystick wanted. I never really had that as my vision for this game as I wanted a more 2.5D feel to the game and a limited number of places you could move to. I got to this point because I was working on the enemy AI scripts using a custom mesh to navigate around.

The game level (or rather endless pattern of a level) is 16 Units wide and two deep.  In this case a Unit is a building component like a piece of floor or doorway or elevator shaft etc.  All these components are 8 x 8 blocks in Unity scaling. This is an example of an elevator shaft:

When you build them all together it looks something like this (that’s a very basic mock up below):

So you got a forward position where you can go up the stairs on the right and a middle position where you can go up the stairs on the left (see they are set back a bit) and a very back position which is through a doorway. So basically there are three parallel positions along the X axis.

What I wanted to do was to create a “patrol point” on every floor space within that grid of a floorplan and also create a patrol point if there is a door that is open.

On the floor positioned at the top or bottom of a stair you do not have a patrol point so there is never anyone at the top or bottom of a stair to block you going up or to knock you back down.

This all gets created at instantiate time and every level is random so I cannot use any of the mesh or nav components that Unity provides.

So all my patrol points get made into lists when the floor is instantiated and added to an array of levels.

When an enemy AI agent starts off they read in all the available patrol point nodes on the floor and work out the available nodes around it to move to.

So the agent knows about the nodes around it in a four square plus it’s own central location. 

As the game mostly scrolls along the left – right axis during game play the nodes are weighted so that travel along the X axis is more likely than the Z. 

At the end of the frame after moving (in late update) the nodes list is refreshed if a new node has been reached.

How does an agent find all the nodes around it?  Using a Raycast is too expensive.  So on each move the agent parses the list of nodes and works out the closest in each direction.

Basically for each node in the list get the x and z and subtract it from your own then put that value in a temporary location – every node gets tested the same way and if the return value is less than the temporary value then you got your closest node in that direction. You would need to do this four times (left, right, forward and back) and handle the null values when there is no space to move next to you. 

At an agent update interval when a new node is reached we first check all the nodes in the list and make a new list of nodes on the floor and our closest points. This gets added to the basic agent control behaviour of “looking around” where the AI stays in one spot and looks left and right in a rotation.  In all cases if they are looking left and the character is right then they cannot pursue him.  If he fires then they will turn.  All of these behaviours are then blended by weight.

I’m not sure if I will continue with this method for the character controller but it’s pretty good for the enemy AI scripts.

Getting a Foot in the Door of Game Design

First of all – sorry about the misleading title – this post is about getting the doors working in the Endless Elevator game that we are currently developing. I thought it was a good pun and that as this post is all about our development process that it wasn’t too bad. The only career advice I got is just to start making games…any games.

You’d think making a door that opens and closes would be a pretty simple thing to do but surprisingly it wasn’t.

I designed and built some 3D models of building components in MagicaVoxel and exported them as .obj files. I use MagicaVoxel because it’s free and really quick and simple to use. When I model components I can be sure that they are all exactly the same scale and that all the different bits (no matter when I made them) are going to fit together without any hassle. But the models are not optimised for game engines as they have a reasonably high poly count due to the modelling process within the program. Most of the models come out with heaps of unnecessary triangles that need to be managed first. In most cases I will import the object into Blender and use the “decimate” modifier on the planes (with an angle of about 20 if you are interested). In the case of the door object it was pretty simple, it is just a door after all, and I didn’t need to optimise it.

Here is what the door looks like in MagicaVoxel:

Notice that the door object is sitting just forward of the enclosing frame and that when exporting the object the center is at the middle bottom plane of that frame. The door is off center because it’s modelled to fit exactly in a doorway that sits exactly in that position within the frame. This saves me a huge amount of time lining everything up when it gets to Unity as the position of the door (or any other object for that matter) is already in the right spot. The problem is that the point of origin for the door is in the wrong spot. It exports a few units behind the door and on the floor! This becomes important when you try and rotate that object (like you would when opening and closing a door) and the pivot point is not where the hinges should be.

To fix this I had to import the .obj file into Blender and reposition the point of origin for the model.

This is what it looks like in Blender when I did this:

To do it I selected the edge that I wanted the door to pivot on when opened.

Then in Edit Mode:
Mesh -> Snap -> Curser to Selected

In Object Mode:
Object -> Transform -> Origin to 3D Curser

So that puts the curser onto the correct position in the middle of that edge where the hinges would be and resets the point of origin (which is where it will pivot when rotated in Unity) to the right spot.

Once we were all imported into Unity and looking good I set up a prefab for the “Doorway” object with the door as a child object. The doorway object has a bunch of colliders to stop the player walking through the walls and a big sphere collider where the door would be to trigger the open / close function when the player walks into it.

This is what the doorway looks like in Unity:

Next I scripted up a few options for opening the door. I’ll post the script at the end of this article but basically there were three options of opening the door that I wanted to test. (Actually I tried it about six ways but whittled it down to the most simple methods – and just as an aside there is an actual “hinge” object type in Unity if you ever need it).

This is how the script looks in the editor:

Notice the slider at the bottom to control how far I want the door to open. It’s really handy to have this when playing in the editor and getting your settings right. If you want to know more about using it see this post

The three tick boxes are for testing the three different ways of opening the door.

Snappy was a quick simple transform of the position from open to closed with no “in betweening”. It looks a bit unnatural as the door magically goes from closed to open but it’s not too bad and most people are pretty used to similar behaviour in video games.

The active line in the code is:
the_door.transform.Rotate(-Vector3.up * 90 * Time.deltaTime);

The next method works more like a door should. In that it swings open and closed the whole way in a steady fashion. But the big problem with this method was that it was OK when the character was going into the doorway and with the swing of the door but when it was time to come back out of the doorway the door was in the way. There was not enough room to trigger the door opening from the other side without being hit by the door as it opened. Plus if the player enters the collider from the wrong trajectory the character gets pushed through a wall by the swinging door which is sub-optimal. I called this method InTheWay!

The active line here is:
the_door.transform.rotation = Quaternion.Euler(targetPositionOpen);

In an effort to combat this I chose to do a hybrid method that opened the door to a point that wouldn’t hit the player and then do the magic transform to get all the way open. I call this one aBitBoth. It looks a little weird too. Like there is an angry fairy pulling the door closed with a snap after the character enters.

Here are all three to compare.

Snappy

In The Way

A Bit of Both

I’m not too sure which one I’m going to use at this stage as the Snappy method works best for now but I like the In The Way method better. I looks more normal and I like that you have to wait just a few milliseconds for the door to swing (adds tension when you are in a hurry to escape a bullet in the back). I could do something like halt the player movement from the rear of the door when it triggers to open from the closed side or maybe play around with the radius of the sphere. Neither solutions seem like great ideas to me right now but something like that will need to be done if I’m going to use that method. Maybe I could just have the door swing both ways and open to the outside when he is behind it but that’s probably a bit weird for a hotel door.

Here is that script that I was testing with:

using UnityEngine;
public class OpenDoor : MonoBehaviour {
public bool openMe;
public GameObject the_door;
public bool snappy;
public bool inTheWay;
public bool aBitBoth;
public Vector3 targetPositionOpen;
public Vector3 targetPositionClosed;
[Range(0F, 180F)]
public float turningOpen;
void Start ()
{
targetPositionClosed = new Vector3(0f, 180f, 0f);
targetPositionOpen = new Vector3(0f, turningOpen, 0f);
}
void Update()
{
if (openMe)
{
OpenMe();
}
else
{
CloseMe();
}
}
private void OpenMe()
{
if (inTheWay)
{
if (the_door.transform.rotation.eulerAngles.y > turningOpen)
{
the_door.transform.Rotate(-Vector3.up * 90 * Time.deltaTime);
}
}
if (snappy)
{
the_door.transform.rotation = Quaternion.Euler(targetPositionOpen);
}
if (aBitBoth)
{
if (the_door.transform.rotation.eulerAngles.y > turningOpen)  // 144f
{
the_door.transform.Rotate(-Vector3.up * 90 * Time.deltaTime);
}
else
{
the_door.transform.rotation = Quaternion.Euler(targetPositionOpen);
}
}
}
private void CloseMe()
{
if (inTheWay)
{
if (the_door.transform.rotation.eulerAngles.y <= 180)
{
the_door.transform.Rotate(Vector3.up * 90 * Time.deltaTime);
}
}
if (snappy)
{
the_door.transform.rotation = Quaternion.Euler(targetPositionClosed);
}
if (aBitBoth)
{
if (the_door.transform.rotation.eulerAngles.y <= turningOpen)  // 144f
{
the_door.transform.Rotate(Vector3.up * 90 * Time.deltaTime);
}
else
{
the_door.transform.rotation = Quaternion.Euler(targetPositionClosed);
}
}
}
void OnTriggerEnter(Collider col)
{
string colName = col.gameObject.name;
Debug.Log("Triggered OpenDoor!!! : " + colName);
if (col.gameObject.name == "chr_spy2Paintedv2" || col.gameObject.name == "BadSpy_Package(Clone)") 
{
openMe = true;
}
}
void OnTriggerExit(Collider col)
{
if (col.gameObject.name == "chr_spy2Paintedv2" || col.gameObject.name == "BadSpy_Package(Clone)") 
{
openMe = false;
}
}
}