My first project that isn't roll-a-ball

After about a days worth of time, I was able to make this. I plan on improving it by making actual levels with some detail using something from the asset store and adding more depth to the gameplay (Like more attacks)but for now, I’m pretty proud of this [even with the errors I haven’t solved yet]. Also, if somebody learning unity as well wants to see the code, I included it in a spoiler tag below the video. The code isn’t commented tho.

PlayerHandler.cs

using UnityEngine;
using UnityEngine.UI;

public class PlayerHandler : MonoBehaviour {

#region Player Variables
public GameObject Player1;
public float P1health = 100;
public GameObject Particles1;
public GameObject Emitter1;
public GameObject Player2;
public float P2health = 100;
public GameObject Particles2;
public GameObject Emitter2;
private Rigidbody rb1;
private Rigidbody rb2;
private bool isFiring1;
private bool isFiring2;
private bool grounded;
private bool grounded2;
private int BulletDirection1;
private int BulletDirection2;
#endregion

#region Shared Variables
public float speed;
public float turnspeed;
public float JumpHeight;
#endregion

#region Others
public GameObject DeathParticles;
public GameObject Bullet;
public float bulletSpeed;
public bool Death;

#endregion

void Fire(){
if(isFiring1 == true){
GameObject Temporary_Bullet_Handler;
Temporary_Bullet_Handler = Instantiate(Bullet,Emitter1.transform.position,Emitter1.transform.rotation) as GameObject;

if(BulletDirection1 == 1){
Temporary_Bullet_Handler.transform.rotation = Quaternion.Euler(0, 0, 90);
}

if(BulletDirection1 == -1){
Temporary_Bullet_Handler.transform.rotation = Quaternion.Euler(0,0,270);
}

Rigidbody Temporary_RigidBody;
Temporary_RigidBody = Temporary_Bullet_Handler.GetComponent();

Temporary_RigidBody.AddForce(new Vector3(BulletDirection1, 0, 0) * bulletSpeed);

Destroy(Temporary_Bullet_Handler, 3.0f);
}
if(isFiring2 == true){
GameObject Temporary_Bullet_Handler;
Temporary_Bullet_Handler = Instantiate(Bullet,Emitter2.transform.position,Emitter2.transform.localRotation) as GameObject;

if(BulletDirection2 == 1){
Temporary_Bullet_Handler.transform.rotation = Quaternion.Euler(0, 0, 90);
}

if(BulletDirection2 == -1){
Temporary_Bullet_Handler.transform.rotation = Quaternion.Euler(0,0,270);
}

Rigidbody Temporary_RigidBody;
Temporary_RigidBody = Temporary_Bullet_Handler.GetComponent();

Temporary_RigidBody.AddForce(new Vector3(BulletDirection2, 0, 0) * bulletSpeed);

Destroy(Temporary_Bullet_Handler, 3.0f);
}

}

void GameOver(){
if(P1health <= 0){
Destroy(Player1);
Instantiate(DeathParticles, Player1.transform.position,DeathParticles.transform.rotation);
}
if(P2health <= 0){
Debug.Log(“P2Dead”);
Destroy(Player2);
Instantiate(DeathParticles, Player2.transform.position, DeathParticles.transform.rotation);
}
}

void Start () {
rb1 = Player1.GetComponent();
rb2 = Player2.GetComponent();
BulletDirection1 = 1;
BulletDirection2 = -1;
isFiring1 = false;
isFiring2 = false;
Death = false;
}

void FixedUpdate () {

if(rb1 != null){
#region Player1Controls
if(rb1.velocity.y <= -0.1 || rb1.velocity.y >= 0.1){
grounded = false;
}
if(rb1.velocity.y == 0){
grounded = true;
}

if(rb1.velocity.x <= -0.2 || rb1.velocity.x >= 0.2){
Particles1.SetActive(true);
}
if(rb1.velocity.x == 0){
Particles1.SetActive(false);
}

//move right
if(Input.GetKey(“d”)){
rb1.AddForce(Vector3.right * speed * Time.fixedDeltaTime);
Player1.transform.rotation = Quaternion.Euler(0, Mathf.Lerp(Player1.transform.rotation.y, 0, turnspeed), 0);
BulletDirection1 = 1;
}
//move left
if(Input.GetKey(“a”)){

rb1.AddForce(Vector3.left * speed * Time.fixedDeltaTime);
//Player1.transform.rotation = Quaternion.Euler(0,180, 0);
Player1.transform.rotation = Quaternion.Euler(0, Mathf.Lerp(Player1.transform.rotation.y, 180, turnspeed), 0);
BulletDirection1 = -1;
}
//move up
if(Input.GetKeyDown(“w”) && grounded == true){
rb1.AddForce(Vector3.up * JumpHeight * Time.fixedDeltaTime);
}
}
#endregion

#region PLayer2Controls
if(rb2 != null){
if(rb2.velocity.y <= -0.1 || rb2.velocity.y >= 0.1){
grounded2 = false;
}
if(rb2.velocity.y == 0){
grounded2 = true;
}
if(rb2.velocity.x <= -0.2 || rb1.velocity.x >= 0.2){
Particles2.SetActive(true);
}
if(rb2.velocity.x == 0){
Particles2.SetActive(false);
}

//move right
if(Input.GetKey(KeyCode.RightArrow)){
rb2.AddForce(Vector3.right * speed * Time.fixedDeltaTime);
Player2.transform.rotation = Quaternion.Euler(0, 0, 0);
BulletDirection2 = 1;
}
//move left
if(Input.GetKey(KeyCode.LeftArrow)){

rb2.AddForce(Vector3.left * speed * Time.fixedDeltaTime);
Player2.transform.rotation = Quaternion.Euler(0,180, 0);
BulletDirection2 = -1;
}
//move up
if(Input.GetKeyDown(KeyCode.UpArrow) && grounded2 == true){
rb2.AddForce(Vector3.up * JumpHeight * Time.fixedDeltaTime);
}

#endregion

}
if(P1health <= 0 && Death == false || P2health <=0 && Death == false){
GameOver();
Death = true;
}
//End of FixedUpdate
}

void Update () {
if(Input.GetKeyDown(KeyCode.Space)){
isFiring1 =true;
Fire();
}
else{
isFiring1 =false;
}
if(Input.GetKeyDown(KeyCode.RightControl)){
isFiring2 =true;
Fire();
}
else{
isFiring2 =false;
}
}

}

UIcontroller.cs

using UnityEngine;
using UnityEngine.UI;

public class UIController : MonoBehaviour {

public Text Wintext;
public Text HealthDisplay;
public GameObject Players;
private PlayerHandler scriptRef;

void Start () {
scriptRef = Players.GetComponent();
Wintext.text = “”;
}

void Update () {
if(HealthDisplay != null){
HealthDisplay.text = "Player1: " + scriptRef.P1health + " Player2: " + scriptRef.P2health;
}
if(scriptRef.P1health <= 0){
Destroy(HealthDisplay);
Wintext.text = “PLAYER 2 HAS WON!”;
}
if(scriptRef.P2health <= 0){
Destroy(HealthDisplay);
Wintext.text = “PLAYER 1 HAS WON!”;
}
}
}

BulletScript.cs

using UnityEngine;

public class BulletScript : MonoBehaviour {

GameObject Players;
public PlayerHandler ph;

void Start () {
Players = GameObject.Find(“Players”);
ph = Players.GetComponent();
}

void OnTriggerEnter(Collider other){
if(other.CompareTag(“Player1”)){
ph.P1health -= 10;
Destroy(gameObject);
}

if(other.CompareTag(“Player2”)){
ph.P2health -= 10;
Destroy(gameObject);
}

if(other.CompareTag(“Environment”)){
Destroy(gameObject);
}
}
}

Any feedback about my code or the project, in general, will be very much appreciated.

Also, every since I started coding, I find myself ending sentences with a semi-colon. That’s weird

Since I was the one who provided the advice to you to combine your player scripts, I feel that I should steer you back on the path before you get the wrong idea.

When I said combine, I didn’t mean handle the two separate objects the same way you were before, but just in one script. What I meant was create a component (let’s call it Player) that you add to both player objects. The Player component will handle the movement and firing for that object and that object alone, and the only thing that needs to be different is detecting what input that controller responds to.

Think about this: If you want to support 4 or 8 players, you don’t want to be creating 8 new fields in your one PlayerHandler class for each player, right? Instead, all you’d have to do is add new instances of your Player object (with its attached Player script) and the only thing you’ll need to handle are the appropriate inputs for that player. There’re a number of ways you can approach input, and it’s something worth investigating on your own to determine what makes the most sense for you, what you want to do, and your budget.

Other than that, I don’t see any huge flags jumping out at me. Understandably, there are a few things that could be cleaned up, optimized, and made more readable, but the point is you got something working! A great exercise as you continue to learn will be to come back and revisit this code every now and then and see what you can improve. If you recognize things as being sloppy, inefficient, or generally “horrible”, it means you’re improving even by your own standards!

Good work so far. Keep at it!

1 Like

I think I understand, basically I have to create one player object and make it into a prefab and find some way to make sure the input method is different for each prefab of the player.
I’m not to sure.
I was thinking maybe make a public variable that changes the input depending on its value.
Thanks for the feedback as always.

Basically, yeah. There are assets like Rewired that make this easy for you. Unity’s working on revamping their Input system, but I haven’t tried that yet so I can’t say whether it’s something that would help or not.

I couldn’t help myself and took a quick stab at your Player script. Added some comments and changes a few things around. This is untested code and is mostly to give you some ideas on where to go with improving things!

// Convention is to have field names in camelCase, and properties (which are a different thing) in PascalCase
// More info here:
// https://msdn.microsoft.com/en-us/library/ms229043(v=vs.100).aspx
public GameObject player;
public float health = 100;
public GameObject particles;
// Since you're only using the position of the emitter, you can just use its Transform
public Transform bulletEmitter;

// If these values are assigned per player, you can have things like powerups that boost
// your speed, jump height, etc!
public float speed;
public float turnSpeed;
public float jumpHeight;

// Don't forget you can assign default values (like you did above) so you can skip initializations in Start/Awake
private Rigidbody rb;
private bool isFiring = false;
private bool grounded;
// Let's store the Quaternion here, since that's all you're actually using
private Quaternion bulletDirection;

public GameObject deathParticles;
public GameObject bullet;
public float bulletSpeed;
public bool Death = false;                // Not really necessary, since you can just check "if (health <= 0)"

void Awake() {
    rb = player.GetComponent<Rigidbody>();
    bulletDirection = Quaternion.Euler(0,0,90);    // Defaults to right
}

void FixedUpdate() {
    // I'd skip the null check on the rigidbody. Awake will always run before Update/FixedUpdate, so it should never be null.
    // If it is, you'll get an error on load and can figure out why there. Why waste the processing power checking it 60 times a second?

    // Input code goes here
}

private void Fire() {
    // Since isFiring is a boolean value, you don't HAVE to compare it to true.
    // And since you named it properly, it still reads clearly
    if (isFiring) {
        // Instantiate has other forms where you can set the rotation, too!
        // https://docs.unity3d.com/ScriptReference/Object.Instantiate.html
        GameObject.Instantiate(bullet, bulletEmitter.position, bulletDirection);
    }

    // The bullet force adding and destroying seems like something that should belong in your bullet script, doesn't it?
    // Try to keep things that are functions performed by an object contained within the scripts for that object,
    // and affect them from outside that only when necessary. This makes debugging much easier.
}

Alright, I’ll redo the script first with my code and see if it works then refer to your code when I’m a having a problem or something else that has to do with the script.
I like to write the code myself that way I fully understand how things work.
Thanks again

1 Like

That’s the correct way! Keep up the good work!

1 Like