Creating a MegaMan style game, writing bullet script, need help moving it across screen.
So I'm creating a Mega Man style game, which will have the regular Mega Buster (standard bullet) and later on 8 unique weapons. As such, on my PlayerController script I have:
public GameObject[] weapons = new GameObject[9];
,at the beginning. I then have a method:
int ChooseWeapon ()
{
return 0;
}
which currenly returns 0, because the only weapon in the game is that standard one that shoots a bullet. (I'll later program a way to choose a different one). Then I have the following method to shoot.
void Shoot (int weapon)
{
if (Input.GetButtonDown ("Fire1")) {
Instantiate (weapons [weapon], new Vector2 (trans.position.x, trans.position.y), Quaternion.identity);
}
}
,with trans being a transform. (Also need to spawn it a bit in front of the player, not right on top, but I'll fix that later) Now I need help with the script that's gonna be attached to the bullet prefab.
public float bulletSpeed;
public Transform trans;
void Start ()
{
trans = GetComponent<Transform> ();
}
void Update ()
{
trans.Translate (/*player x*/ * bulletSpeed, /*player y*/, 0);
}
What's the best way to get the player's position, or to say it's traveling from the starting position where it's instantiated.
Feel free to ask for more code, tough there isn't much and thanks for the help in advance.
Answer by Catralitos · Mar 13, 2018 at 06:01 PM
@SeigneurNecron Seems to work mostly fine, but the bullets still will only shoot in one direction.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float jumpLimit;
public float jumpPower;
public float speed;
public float step;
public float fireRate;
public float nextFire;
public float bulletSpeed;
private bool facingRight = true;
private bool isOnGround = true;
private const float BULLET_SPAWN_OFFSET = 0.5f;
public Animator anim;
public GameObject[] weapons = new GameObject[9];
public Rigidbody2D rb;
public Transform trans;
public bool FacingRight {
get;
private set;
}
void Awake ()
{
anim = GetComponent<Animator> ();
rb = GetComponent<Rigidbody2D> ();
trans = GetComponent<Transform> ();
}
void Update ()
{
MovePlayer ();
Jump ();
Shoot (ChooseWeapon ());
}
void MovePlayer ()
{
float translate = Input.GetAxisRaw ("Horizontal") * speed * Time.deltaTime;
trans.Translate (translate, 0, 0);
if (translate > 0) {
anim.SetTrigger ("PlayerRunRight");
if (!facingRight)
Flip ();
}
if (translate < 0) {
anim.SetTrigger ("PlayerRunLeft");
if (facingRight)
Flip ();
}
if (translate == 0)
anim.SetTrigger ("PlayerStop");
}
void Jump ()
{
if (Input.GetButtonDown ("Jump") && isOnGround) {
rb.AddForce (new Vector2 (0, jumpPower), ForceMode2D.Impulse);
}
}
void Flip ()
{
facingRight = !facingRight;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
//both of these are wrong I know, but I'm a newbie, this can //be saved for later, just testing out character controll on a flat //surface for now
void OnCollisionEnter2D ()
{
isOnGround = true;
}
void OnCollisionExit2D ()
{
isOnGround = false;
}
int ChooseWeapon ()
{
return 0;
}
void Shoot (int weapon)
{
if (Input.GetButtonDown ("Fire1")) {
Vector3 spawnLocation = this.transform.position + new Vector3(BULLET_SPAWN_OFFSET * (this.facingRight ? -1 : 1), 0f, 0f);
MegaBusterBullet bullet = Instantiate(this.weapons[this.ChooseWeapon()], spawnLocation, Quaternion.identity).GetComponent<MegaBusterBullet>();
bullet.FacingRight = this.FacingRight;
}
}
}
With the bullet script being
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MegaBusterBullet : MonoBehaviour {
public float bulletSpeed;
private Rigidbody2D rigidBody;
public bool FacingRight {
get;
set;
}
public float MoveSpeed {
get
{
return this.bulletSpeed * (this.FacingRight ? -1 : 1) * Time.deltaTime;
}
}
private void Awake() {
this.rigidBody = this.GetComponent<Rigidbody2D>();
}
private void FixedUpdate() {
this.rigidBody.MovePosition(this.transform.position + new Vector3(this.MoveSpeed, 0f, 0f));
}
}
Currently no matter what direction I'm turned to (I'm flipping my character well, so facingRight must be working) I'm shooting my bullets to the right. Can't shoot left.
$$anonymous$$y theory is that the getter might be wrong. facingRight is changing it's value properly in Flip(), so for the bullets to go only in one direction, they must not be getting facingRight properly.
In fact, writing this :
public bool FacingRight {
get;
private set;
}
is equivalent to :
private bool fieldThatYouCantAccessDirectly = false;
public bool FacingRight {
get {
return this.fieldThatYouCantAccessDirectly;
}
private set {
this.fieldThatYouCantAccessDirectly = value;
}
}
Hence, in this case FacingRight
has nothing to do with facingRight
. This is called an Auto Property.
public bool FacingRight {
get
{
return facingRight;
}
}
Changed the getter in the PlayerController to this and it seems to be working (actually the reverse directions, but for that just gotta change where the $$anonymous$$us is).
The problem here is that you are sometimes using the field
facingRight
and sometimes using the propertyFacingRight
. A good way to avoid doing this kind of mistakes is to only use the field inside the property, and update everything that is affected inside the property.I also noticed you were potentially triggering "PlayerStop" a lot when not needed.
You should also replace all instances of
(this.FacingRight ? -1 : 1)
by(this.FacingRight ? 1 : -1)
I suggest you to do something like this:// $$anonymous$$eep this : private bool facingRight = true; // Add this : private bool moving = false; // $$anonymous$$odifiy this : public bool FacingRight { get { return this.facingRight; } private set { if(this.facingRight != value) { this.facingRight = value; anim.SetTrigger(this.facingRight ? "PlayerRunRight" : "PlayerRunLeft"); Vector3 theScale = transform.localScale; theScale.x *= -1; transform.localScale = theScale; } } } // Add this : public bool $$anonymous$$oving { get { return this.moving; } private set { if(this.moving != value) { this.moving = value; if(!this.moving) { anim.SetTrigger("PlayerStop"); // Doing it this way will ensure the trigger only happens when $$anonymous$$oving goes from true to false, ins$$anonymous$$d of happening every frame while the player is not moving. } } } } // $$anonymous$$odifiy this too : void $$anonymous$$ovePlayer() { float translate = Input.GetAxisRaw("Horizontal") * speed * Time.deltaTime; transform.Translate(translate, 0, 0); if(translate == 0) { this.$$anonymous$$oving = false; } else { this.$$anonymous$$oving = true; this.FacingRight = translate > 0; } } // Remove this : void Flip() { facingRight = !facingRight; Vector3 theScale = transform.localScale; theScale.x *= -1; transform.localScale = theScale; }
Seems to work fine, but sometimes the animations just won't play, and there's a tiny recoil when you shoot a bullet, that I'm not really a fan of. I don't really know why sometimes the animations play and sometimes don't seems kinda random.
Weird... I don't see how this modifications can cause a recoil. For the animations that don't always play, it's hard to tell without seeing the animator transitions.
Answer by SeigneurNecron · Mar 12, 2018 at 11:46 PM
Don't use
Update()
, it's frame rate dependent, use the physic updateFixedUpdate()
instead.Multiply your bullet speed by
Time.deltaTime
to convert from distance/second to distance/update.Don't use the player position when updating the bullet position, the player and bullet will move independently, use the current bullet
transform.position
instead.You don't need to use
GetComponent<Transform>()
, the transform is always available in thetransform
field of any class extendingComponent
.You also need to know if the bullet is going to the left or the right, you can store that in a public boolean property
Direction
for example, so can set it from you player script, and it won't appear in the inspector.You should not update the bullet transform position directly, but use
Rigidbody2D.MovePosition()
instead, so the collisions are handled properly. You will need to attach aRigidBody2D
component to your bullet prefab (Body Type: Kinematic) and store it in a field like you did for the transform. (There is arigidbody
field but it's deprecated). That field should be private, you don't need to modify its value from outside of the bullet script.
So the bullet script should look like this:public class MegaBusterBullet : MonoBehaviour { public float bulletSpeed; private Rigidbody2D rigidBody; public bool Direction { get; set; } public float MoveSpeed { get { return this.bulletSpeed * (this.Direction ? -1 : 1) * Time.deltaTime; } } private void Awake() { this.rigidBody = this.GetComponent<Rigidbody2D>(); } private void FixedUpdate() { this.rigidBody.MovePosition(this.transform.position + new Vector3(this.MoveSpeed, 0f, 0f)); } }
When you instantiate your bullet in your player script, don't forget to set its direction to be the same as the player's current direction.
The player script should look like this:public class Player : MonoBehaviour { private const float BULLET_SPAWN_OFFSET = 0.5f; [SerializeField] private GameObject[] weapons = new GameObject[9]; public bool Direction { get; private set; } public int EquipedWeapon { get; private set; } private void Update() { if(Input.GetButtonDown("Fire1")) { this.Shoot(); } if(Input.GetButtonDown("SwitchWeapon0")) { this.EquipedWeapon = 0; } } private void Shoot() { Vector3 spawnLocation = this.transform.position + new Vector3(BULLET_SPAWN_OFFSET * (this.Direction ? -1 : 1), 0f, 0f); MegaBusterBullet bullet = Instantiate(this.weapons[this.EquipedWeapon], spawnLocation, Quaternion.identity).GetComponent<MegaBusterBullet>(); bullet.Direction = this.Direction; } }
Hope this helps. :)
dont use time.deltaTime in fixed update, since deltatime is the time between normal update.
https://docs.unity3d.com/ScriptReference/Time-deltaTime.html
When called from inside $$anonymous$$onoBehaviour's FixedUpdate, returns the fixed framerate delta time
So does that mean I turn the FixedUpdate() in the $$anonymous$$egaBuster script to an Update() or do I touch something else. Because the Time.DeltaTime used in moveSpeed() is the only one that I could say is being used on FixedUpdate(), since the value that returns is used in that update method.
What @Rygaran wanted to say, is that you should use Time.fixedDeltaTime
ins$$anonymous$$d of Time.deltaTime
. But when called in the FixedUpdate() method, Time.deltaTime
returns Time.fixedDeltaTime
anyway. So you don't have to touch anything.