I've been stuck with this nightmare for a while, and it's certainly my perfectionism. What I have is a cannon that fires a shell from an object pool. The order of operations is as follows.
- The disabled pooled object is SetActive
- The pooled Object's forward vector is set to match the cannon's forward vector (previously also used rotations)
- The pooled Object's position is set to the tip of the barrel (previously from another transform that was placed at the tip of the barrel, now done via a field, this caused 0 difference in the issue)
- The pooled Object's velocity is set based on its now changed forward vector.
After all this is done, the shell is supposed to be perfectly accurate. Instead, the shell appears to come into existence offset from the barrel tip, when a Gizmo given the same transform has no such issue. The shell's trajectory is also incorrect, as the barrel's forward vector and the shell's forward vector deviate before the shell even has its first frame of flight. I have attached a screenshot of the issue, and the hierarchy of objects which shows where the cannon (which gives the shell its position) is.
It should also be noted that, for some reason, this problem is less severe (but still present) when the game is built, and is far worse in editor.
Another very important note is that the pivot point of the shell, and the mesh center, are in fact in the center of the shell. I double checked this by instantiating primitive spheres in the position and that creates an identical result.
Here is a (simplified) version of the code which creates and fires the shell.
Code is applied relative to the hierarchy as follows:
Player (parent of PlayerVehicle, not pictured) contains PlayerController
Player Vehicle contains a rigidbody
120mm_Mid_Long contains CannonBehavior
Shell (not pictured, no parent in hierarchy) contains ShellBasic and ShellPhysics
PlayerController : MonoBehaviour
{
Player player;
public KeyCode unlockMouse = KeyCode.LeftAlt;
void Start()
{
this.player = GetComponent<Player>();
}
void Update()
{
isUnlocked = !Input.GetKey(unlockMouse);
//Input used to drive vehicle is in this section
if (isUnlocked)
{
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
player.turretAim.GetInput(Input.GetAxis("Mouse X") * sensitivity * Time.deltaTime, Input.GetAxis("Mouse Y") * sensitivity * Time.deltaTime);
player.turretAim.AimTurret();
}
//else statement exists, just changes cursor lock state and visibility
if(Input.GetButton("Fire1")
{
player.cannon.TryFireShell();
//player contains CannonBehavior
}
}
}
public class CannonBehavior : MonoBehaviour
{
public Vector3 ShellFireOffset => GetShellOffset();
public void TryFireShell()
{
//checks to see if cannon can fire, then gets a shell from the pool
ShellBasic shellBehavior = shell.GetComponent<ShellBasic>();
ShellPhysics shellPhys = shellBehavior.simplifiedPhysics;
shell.SetActive(true);
shellBehavior.Reset(ShellFireOffset, transform.forward.normalized);
}
public Vector3 GetShellOffset()
{
//going to use transforms here later
return this.transform.position + this.transform.forward.normalized * this.fireOffset.z + this.transform.right.normalized * this.fireOffset.x + this.transform.up.normalized * this.fireOffset.y;
}
}
public class ShellBasic : MonoBehaviour
{
public ShellPhysics simplifiedPhysics;
void Awake()
{
this.simplifiedPhysics = this.GetComponent<ShellPhysics>();
}
public void Reset(Vector3 position, Vector3 direction)
{
this.simplifiedPhysics.FireFromPosition(position, direction, velocity);
}
}
public class ShellPhysics : MonoBehaviour
{
public Vector3 velocity;
void FixedUpdate()
{
this.velocity += Physics.gravity * Time.fixedDeltaTime;
this.transform.forward = this.velocity.normalized;
this.transform.position += this.velocity * Time.fixedDeltaTime;
}
public void FireFromPosition(Vector3 position, Vector3 direction, float velocity)
{
this.transform.forward = direction;
this.transform.position = position;
this.velocity = this.transform.forward.normalized * velocity;
}
}

