1

I stumbled across a problem while working with the book "Unity in Action". At the end of chapter 3 you'd end up with the basics for a simple fps game. It's basically a player (camera attached to it) in a simple and small level which only exists out of a number of cubes forming walls and the floor etc. These cubes all have box collider on them. Now the player is also able to shoot at moving enemies which are also able to shoot. This was done by Raycast/RaycastHit. All this worked perfectly so I wanted to add something that reassembles bullet holes, simply by instantiating a black sphere object on the wall where the fireball (this is the object the enemy and the player shoot) hits it. This works BUT sometimes the fireball object just goes through the wall. In case there is another wall behind it, the fireball object gets destroyed by this second wall and the desired sphere is created on the second wall instead of on the first wall.

I changed the speed in the fireball from 20 to 10 and then back to 20 and at a speed of 10, the success rate is around 19/20, while at a speed of 20 it's around 6/10.

Here is the code of the fireball which is supposed to check whether it hits the player (then health is deducted, that works fine) or hits the enemy (then the enemy falls over, also works fine) or hits the wall in which case the sphere should be created.

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

 public class Fireball : MonoBehaviour {

 public float speed = 10.0f;
 public int damage = 1;
 [SerializeField] private GameObject wallHit;
 private GameObject _wallHit;

 // Use this for initialization
 void Start () {

 }

 // Update is called once per frame
 void Update () {
     transform.Translate(0,0, speed * Time.deltaTime);
 }

 void OnTriggerEnter(Collider other){
     RaycastHit hit;
     PlayerCharacter player = other.GetComponent<PlayerCharacter>();
     ReactiveTarget target = other.GetComponent<ReactiveTarget>();
     WallBehavior wall = other.GetComponent<WallBehavior>();

     if(player != null){
         player.Hurt(damage);
     }
     if(target != null){
         target.ReactToHit();
     }
     if(wall != null){
         if(Physics.Raycast(transform.position, transform.forward, out hit)){
             wall.WallWasHit(hit);
         }
     }
     Destroy(this.gameObject);
 }
}

As you can see one thing I tried was to give each wall a WallBehavior script, which looks like this:

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

 public class WallBehavior : MonoBehaviour {
 [SerializeField] private GameObject wallHit;
 private GameObject _wallHit;
 static int count = 0;

 // Use this for initialization
 void Start () {

 }

 // Update is called once per frame
 void Update () {

 }
 public void WallWasHit(RaycastHit hit){
             count++;
             Debug.Log("Wall was hit: " + count);

             _wallHit = Instantiate(wallHit) as GameObject;
             _wallHit.transform.position = hit.point;
 }
 }

But none of my attempts to understand why this happens so infrequently was successful so far and I hope someone can help me with this because I feel like might be crucial for my learning before I continue with the book! Thanks in advance. if further information are needed, I am happy to provide them. See the following picture for a better visualization of the issue. Six spheres on the wall, one fireball still in the air

EDIT: As mentioned in the answers, I replaced the fireball script accordingly but I am not sure how to change the following script likewise. This script is on my camera which is on my player object. In the Update function the Fireball is instantiated and also given a movement, so I guess this causes the problems?

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

public class RayShooter : MonoBehaviour {
private Camera _camera;

[SerializeField] private GameObject fireballPrefab;
private GameObject _fireball;


void Start () {
    _camera = GetComponent<Camera>();

    Cursor.lockState = CursorLockMode.Locked;
    Cursor.visible = false;
}

void OnGUI(){
    int size = 12;
    float posX = _camera.pixelWidth/2 - size/4;
    float posY = _camera.pixelHeight/2 - size/2;
    GUI.Label(new Rect(posX, posY, size, size), "X");
}

// Update is called once per frame
void Update () {
    if(Input.GetMouseButtonDown(0)){
        Vector3 point = new Vector3(_camera.pixelWidth/2, _camera.pixelHeight/2, 0);
        _fireball = Instantiate(fireballPrefab) as GameObject;
        _fireball.transform.position = transform.TransformPoint(Vector3.forward * 1.5f);
        _fireball.transform.rotation = transform.rotation;

        Ray ray2 = _camera.ScreenPointToRay(point);
        RaycastHit hit;
        if(Physics.Raycast(ray2, out hit)){
            GameObject hitObject = hit.transform.gameObject;
            ReactiveTarget target = hitObject.GetComponent<ReactiveTarget>();
            if(target !=null){
                target.ReactToHit();
            } 
        }
    }
}
}
marvinb89
  • 83
  • 9

1 Answers1

3

This is not how to move a Rigidbody Object and moving it like this could cause so many issues including the one mentioned in your question. Objects with Rigidbody should be moved with the Rigidbody component and with the functions like Rigidbody.MovePosition, Rigidbody.AddForce and Rigidbody.velocity not by the transform or transform.Translate.

Also, you should move the Rigidbody Object in the FixedUpdate function instead of the Update function. If you are moving your other Rigidbody Objects by their transform then you must fix them too. The example below replaces transform.Translate with Rigidbody.MovePosition in the Fireball script:

public float speed = 10.0f;
public int damage = 1;
[SerializeField]
private GameObject wallHit;
private GameObject _wallHit;

public Rigidbody rb;

// Use this for initialization
void Start()
{
    rb = GetComponent<Rigidbody>();
}

// Update is called once per frame
void FixedUpdate()
{
    //Move to towards Z-axis
    Vector3 pos = new Vector3(0, 0, 1);
    pos = pos.normalized * speed * Time.deltaTime;

    rb.MovePosition(rb.transform.position + pos);
}

void OnTriggerEnter(Collider other)
{
    RaycastHit hit;
    PlayerCharacter player = other.GetComponent<PlayerCharacter>();
    ReactiveTarget target = other.GetComponent<ReactiveTarget>();
    WallBehavior wall = other.GetComponent<WallBehavior>();

    if (player != null)
    {
        player.Hurt(damage);
    }
    if (target != null)
    {
        target.ReactToHit();
    }
    if (wall != null)
    {
        if (Physics.Raycast(transform.position, transform.forward, out hit))
        {
            wall.WallWasHit(hit);
        }
    }
    Destroy(this.gameObject);
}

If you still run into issues, use Rigidbody.velocity instead:

void FixedUpdate()
{
    Vector3 pos = Vector3.zero;
    pos.z = speed * Time.deltaTime;

    rb.velocity = pos;
}

Sometimes, depending on the size of the object and the speed it is moving by, you many need to set its Rigidbody Interpolate from None to Interpolate and Collision Detection to Continuous.

Don't forget to remove the code in the Update function.


EDIT:

I looked at your project and found new problems:

1.You enabled IsTrigger on your "Fireball" GameObject. Please uncheck the IsTrigger on the collider.

2.You just want to shoot a bullet. The force should be added once only not every FixedUpdate. Add the force in the Start function instead of the FixedUpdate function. Use Rigidbody.velocity instead of Rigidbody.MovePosition.

Remove the FixedUpdate function and the code inside it.

This should be your new Start function:

void Start()
{
    rb = GetComponent<Rigidbody>();

    Vector3 pos = transform.forward * speed * Time.deltaTime;
    rb.velocity = pos;
}
Programmer
  • 121,791
  • 22
  • 236
  • 328
  • Thanks for your help, appreciate it. I changed the fireball code accordingly but then the fireballs are instantiated but they don't move at all and just appear in the center of the level. So I tried your velocity solution. But in this case the fireball only moves into one direction in this case z and doesn't move into the direction I "shoot" The last option addForce kind of solves this issue but just like all the other options, some fireballs go through the wall like before. I dont have any other rigidbody objects in my level. Again, thanks for the time so far! – marvinb89 Apr 09 '18 at 05:24
  • I fixed the "they don't move at-all issue". `MovePosition` should now work. See the new code. Note that all I did is to make it move properly and simply try to port `transform.Translate(0,0, speed * Time.deltaTime);`. You probably don't know this but `transform.Translate(0,0, speed * Time.deltaTime);` is supposed to be moving in the z axis since x and y are set to 0. That's the exact port I made. Is it suppised to be doing something else? – Programmer Apr 09 '18 at 07:14
  • What I meant is, it doesn't matter where the camera is pointing at. So I might look in one direction and shoot, but once the fireball is instantiated, it moves backwards instead of forwards. But I fixed this issue with the following code: void FixedUpdate(){ rb.MovePosition(transform.position + transform.forward * Time.deltaTime * speed); } This way the movement is correct and at a rate of 10 all the fireballs create spheres on the wall. But the higher the speed, the more go through. But I found something else that might be the root of all evil, please see my edited question – marvinb89 Apr 09 '18 at 16:01
  • Well you didn't explain the direction the instantiated object is supposed to go so I used what your code is currently doing to fix that. Glad you fixed that part. Your `RayShooter` script is fine since you are moving the postion only once after being instantiated instead of every frame. Resize the thickness of the walls collider. You can do that without even resizing the size of the mesh or object as a whole. If this doesn't work, upload the project somewhere and provide link here and I will take a look at it. You must also explain how to re-creat the problem – Programmer Apr 09 '18 at 20:05
  • Ok so, what's supposed ti happen when you instantiate the ball and it hits the wall? Bounce back and drop? Destroy itself? Please explain. You can now remove the project link from above. – Programmer Apr 10 '18 at 11:15
  • So as said at the very beginning, the idea was to create bulletholes that are visible on the wall. Of course these spheres don't look anything like bulletholes but I wanted to figure out the mechanics before the looks. So long story short, when the ball hits the wall it should get destroyed AND create this sphere on the wall. And it does...but not all the time, especially when increasing the speed most fireballs go through the wall, get destroyed and don't leave the desired sphere as a mark. – marvinb89 Apr 10 '18 at 13:05
  • See the edit in my answer. I discovered few issues and mentioned the fix there. – Programmer Apr 12 '18 at 02:50
  • Thanks again for the effort. Did it work for you with this approach? Because I still have the exact same issue actually. So Is Trigger on the fireball is unchecked, I replaced the code in the start function and got rid of the FixedUpdate function. If I shoot now the fireball is quite slow and again then it works perfectly. But if I increase the speed to like 300 because it is supposed to be a bullet-like object with a certain speed, I am back to the old issue. I uploaded a video of the behavior in case this is not an issue when you play the scene: https://youtu.be/uKnHRQTeGpU Thanks again! – marvinb89 Apr 12 '18 at 05:57
  • If it is slow, increase the speed. It works fine for me. Send the new project to me again. I will try it and report back to you now. – Programmer Apr 12 '18 at 05:58
  • The project you sent me worked fine. It never went through walls. I noticed that we are using different Unity versions. Go to Edit-->Project Settings--> Time then change `Fixed Timestep` to `0.01` and see what happens. Also, go to Edit-->Project Settings--> Physics then change `Default Solver Iterations` to `9` and see what happens. If non of these fixed the issue, please update Unity to at-least my version. I am using Unity 2017.2.0f3. Anything above that should be fine. – Programmer Apr 12 '18 at 07:05
  • Chapeu! Now it works. I changed the Fixed Timestep to 0.01 and was able to see that the sphere creation was much more reliable than before. After increasing the speed further again the problem was back so I changed the Fixed Timestep to 0.001 and now it works perfectly no matter which speed! So at this point I can just say thank you very much, really appreciate your help and effort, I didn't expect to find someone online who is wiling to put up that much of an effort for such a beginner-project!!! – marvinb89 Apr 12 '18 at 08:02
  • You are welcome. I help people if they put effort into their work. In your case, you did so I didn't see why I should stop helping. Happy coding! – Programmer Apr 12 '18 at 08:07