So . I work at a 2d game in unity and I have some problems with some mechanics and i really need some help from you guys. All I want to do is to make my character hand to stop and grab a object. When I press E the animation of the hand start and have a collider for each frame, in case that the hand collide with a closer object, the animation to stop at that frame. I have a week since I try to figure it out. If you want to help me we can do it on discord. I will put the codes here, maybe the reason that I can't to what I want to do is very clear and I dont see it.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class showHide : MonoBehaviour
{
[SerializeField]
GameObject hand;
private Animator freeze;
public bool touch = false;
private bool ok = true;
// Start is called before the first frame update
void Start()
{
freeze = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.E) && ok == true)
{
freeze.Play("Follower");
StartCoroutine(show());
}
}
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "box")
{
StartCoroutine(colliderShow());
}
}
private IEnumerator show()
{
ok = false;
hand.SetActive(true);
yield return new WaitForSeconds(4f);
hand.SetActive(false);
ok = true;
}
private IEnumerator colliderShow()
{
touch = true;
print(touch);
yield return new WaitForSeconds(4f);
touch = false;
print(touch);
}
private void FreezeAniamtion()
{
freeze.speed = 0f;
StartCoroutine(waitTime());
}
private IEnumerator waitTime()
{
yield return new WaitForSeconds(0f);
freeze.speed = 1f;
}
}
This is the code that activate and deactivate my hand object , including the animation .
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class facingScript : MonoBehaviour
{
private SpriteRenderer render;
private Camera mainCam;
private Vector3 mousePos;
private Animator aniHand;
public bool atinge;
void Start()
{
mainCam = Camera.main;
render = GetComponent<SpriteRenderer>();
aniHand = GetComponent<Animator>();
gameObject.SetActive(false);
}
// Update is called once per frame
void Update()
{
mousePos = Input.mousePosition;
mousePos = mainCam.ScreenToWorldPoint(
new Vector3(mousePos.x, mousePos.y, mainCam.transform.position.z - transform.position.z)
);
Vector3 rotation = mousePos - transform.position;
if (rotation.x > 0)
render.flipY = true;
else
render.flipY = false;
if (atinge == false)
aniHand.speed = 1f;
Debug.Log(aniHand.speed);
}
// Collision with objects
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "box")
{
atinge = true;
aniHand.speed = 0f;
StartCoroutine(freezingTime());
}
}
private IEnumerator freezingTime()
{
yield return new WaitForSeconds(4f);
atinge = false;
}
// No collision with any object
private void FreezeAnimation()
{
aniHand.speed = 0f;
StartCoroutine(waitTime());
}
private IEnumerator waitTime()
{
yield return new WaitForSeconds(0f);
aniHand.speed = 1f;
}
}
This is the hand component script . When the hand don't collide with anything the object is deactivated . The FreezeAnimation() is a event at the last frame. I tryed to do a collider animation for the showHide component that will be exact with the hand animation collider for each frame to check if the hand collide in the showHide script and if it collide to have a 4 seconds of waiting with the hand at that position.
I really tryed my best but I really can't figure it out.
So I'm using Astar pathfinding project plugin and I have a target Transform, which is a point where the AI builds its path.
Currently I'm trying to implement a roaming state to my enemy. I've decided to create a random point and just shove it to target, but every solution I know or I've found regarding creating a random point is in either Vector2 or Vector3.
How to create a random point and reference it to Transform variable?
I've tried this solution from Astar project's tutorual (Method 1), but that didn't work for me. Here's almost the whole code just in case. The problem is in a state machine in a state Roaming.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Pathfinding;
public class EnemyAI : MonoBehaviour
{
private Transform target; //that's what's all the fuss about
private GameObject player;
public float speed = 200f;
public float nextWaypointDistance = 3f;
public float radius;
private float repeatPath = .5f;
enum State
{
Roaming,
ChaseTarget,
Attacking,
}
private State state;
Path path;
int currentWaypoint;
bool reachedEndOfDestination = false;
private Animator animator;
private float horizontal;
private float vertical;
private Vector2 force;
private GameObject gfx;
private bool setAttackTimer;
private float attackTimer;
Seeker seeker;
Rigidbody2D rb;
private void Awake()
{
//state = State.Roaming;
}
void Start()
{
seeker = GetComponent<Seeker>();
rb = GetComponent<Rigidbody2D>();
gfx = GameObject.Find("CQBgfx");
animator = gfx.GetComponent<Animator>();
player = GameObject.Find("Player");
target = player.transform;
state = State.ChaseTarget;
InvokeRepeating("UpdatePath", 0f, repeatPath);
}
void UpdatePath()
{
if (seeker.IsDone())
seeker.StartPath(rb.position, target.position, OnPathComplete); //Here's the plugin forces me to use Transform
}
void OnPathComplete(Path p)
{
if (!p.error)
{
path = p;
currentWaypoint = 1;
reachedEndOfDestination = false;
}
}
void FixedUpdate()
{
Movement();
MovementAnimator();
}
private void Update()
{
switch (state)
{
default:
case State.Roaming:
Vector3 PickRandomPoint () {
var point = Random.insideUnitSphere * radius;
point.y = 0;
point += transform.position;
return point;
}
target = PickRandomPoint(); //Error. Vector3 cannot be converted to Transform.
break;
case State.ChaseTarget:
target = player.transform;
break;
case State.Attacking:
break;
}
}
void Movement()
{
if (path == null)
return;
if (currentWaypoint >= path.vectorPath.Count)
{
reachedEndOfDestination = true;
rb.velocity = Vector2.zero;
force = Vector2.zero;
return;
}
else
{
reachedEndOfDestination = false;
}
if (!reachedEndOfDestination)
{
Vector2 direction = ((Vector2)path.vectorPath[currentWaypoint] - rb.position).normalized;
force = direction * speed * Time.deltaTime;
rb.AddForce(force);
}
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
if (distance < nextWaypointDistance)
currentWaypoint++;
}
}
If you absolutely HAVE to use a transform instance, (as I'm guessing you use transform.position to continuously follow a target) creating an empty GameObject and moving that to the target would work.
private GameObject roamTarget; // <-- new roamTarget variable
private Transform target;
private GameObject player;
//...rest of variables
void OnEnable()
{
// create roamTarget instance and reuse it for later
roamTarget = new GameObject(gameObject.name + " Roam Target"); //just gives it a readable name
}
void OnDisable()
{
// destroy our roamTarget on disable so we don't pollute our scene with unused gameobjects
Destroy(roamTarget);
}
and
private void Update()
{
switch (state)
{
default:
case State.Roaming:
roamTarget.transform.position = PickRandomPoint();
target = roamTarget;
break;
//...rest of switch cases
}
//...rest of Update()
}
Just make sure to create one and move it around, instead of constantly instantiating and destroying targets,
I have an object structure like this:
Which I have to create a player with several different equipment. For this, I am using a custom animation controller which changes an index that would determine sprites out of a few spritesheets:
AnimationController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AnimationController : MonoBehaviour {
private float waitTime = 0.06f;
private float timer = 0.0f;
private bool grounded;
private bool falling;
private bool jumping;
private bool running;
private bool horizontalCollision;
private HeroMovement heroMovementScript;
private GameObject hero;
private int[] runningSprites = { 2, 3, 4, 5, 6, 7, 8, 13, 14, 15, 16, 17, 18, 19 };
private int animationIndex = 0;
public int currentHeroSpriteIndex;
// Start is called before the first frame update
void Start() {
hero = GameObject.Find("Hero");
heroMovementScript = hero.GetComponent<HeroMovement>();
}
// Update is called based on timer and waitTime
void Update() {
timer += Time.deltaTime;
if (timer > waitTime) {
grounded = heroMovementScript.isGrounded;
falling = heroMovementScript.isFalling;
jumping = heroMovementScript.isJumping;
running = heroMovementScript.isRunning;
horizontalCollision = heroMovementScript.horizontalCollision;
if (running) {
currentHeroSpriteIndex = runningSprites[animationIndex % 14];
animationIndex++;
} else {
currentHeroSpriteIndex = 0;
animationIndex = 0;
}
timer = timer - waitTime;
}
}
}
So here I run the update function a set wait time, and based on variables from a HeroMovement script do I update the booleans to determine how to update the index based on declared arrays:
HeroMovement.cs
using UnityEngine;
public class HeroMovement : MonoBehaviour {
[SerializeField] private float speed;
[SerializeField] private float jumpHeight;
private Rigidbody2D body;
private Animator anim;
private AnimationController animationControllerScript;
private int currentHeroSpriteIndex;
private SpriteRenderer heroRenderer;
private HeroResources heroResourcesScript;
private Sprite[] heroSprites;
public bool isGrounded;
public bool isFalling;
public bool isJumping;
public bool isFacingLeft;
public bool isRunning;
public bool horizontalCollision;
public int collisionCounter = 0;
// called when script is loaded
private void Awake() {
body = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
animationControllerScript = GetComponent<AnimationController>();
heroRenderer = GetComponent<SpriteRenderer>();
heroResourcesScript = GetComponent<HeroResources>();
currentHeroSpriteIndex = animationControllerScript.currentHeroSpriteIndex;
heroSprites = heroResourcesScript.heroSprites;
}
// called on every frame of the game
private void Update() {
float horizontalInput = Input.GetAxis("Horizontal");
float verticalSpeed = body.velocity.y;
// x axis movement
if (!horizontalCollision) {
body.velocity = new Vector2(horizontalInput * speed, body.velocity.y);
// flip player when moving left
if (horizontalInput > 0.01f && isGrounded) {
transform.localScale = Vector3.one;
isFacingLeft = false;
}
// flip player when moving right
else if (horizontalInput < -0.01f && isGrounded) {
transform.localScale = new Vector3(-1, 1, 1);
isFacingLeft = true;
}
}
// jumping
if (Input.GetKey(KeyCode.Space) && isGrounded) {
Jump();
}
isRunning = horizontalInput != 0 && !isJumping && !isFalling;
// set animator parameters
// anim.SetBool("isRunning", horizontalInput != 0 && !isJumping && !isFalling);
// anim.SetBool("isGrounded", isGrounded);
// anim.SetBool("isFalling", isFalling);
// anim.SetBool("isJumping", isJumping);
// anim.SetBool("horizontalCollision", horizontalCollision);
if (!isGrounded && verticalSpeed < -1) {
Fall();
}
}
private void Fall() {
isFalling = true;
}
private void Jump() {
body.velocity = new Vector2(body.velocity.x, jumpHeight);
isJumping = true;
isGrounded = false;
}
private void OnCollisionEnter2D(Collision2D collision) {
Collider2D collider = collision.collider;
Collider2D otherCollider = collision.otherCollider;
if (collision.gameObject.tag == "Ground") {
if (otherCollider.tag == "Hero") {
if (!isHorizontalCollision(otherCollider, collider)) {
isGrounded = true;
isFalling = false;
isJumping = false;
horizontalCollision = false;
} else {
horizontalCollision = true;
if (isBottomCollision(otherCollider, collider)) {
horizontalCollision = false;
}
}
}
}
collisionCounter++;
}
private bool isBottomCollision(Collider2D collider1, Collider2D collider2) {
int c1BottomEdge = (int) collider1.bounds.max.y;
int c2TopEdge = (int) collider2.bounds.min.y;
return c1BottomEdge == c2TopEdge;
}
private bool isHorizontalCollision(Collider2D collider1, Collider2D collider2) {
int c1RightEdge = (int) collider1.bounds.max.x;
int c1LeftEdge = (int) collider1.bounds.min.x;
int c2RightEdge = (int) collider2.bounds.max.x;
int c2LeftEdge = (int) collider2.bounds.min.x;
return (c1RightEdge == c2LeftEdge) || (c1LeftEdge == c2RightEdge);
}
private void OnCollisionExit2D(Collision2D collision) {
collisionCounter--;
if (collisionCounter == 0) {
isGrounded = false;
}
}
// private bool isGrounded() {
// RaycastHit2D raycastHit = Physics2D.BoxCast(boxCollider.bounds.center, boxCollider.bounds.size, 0, Vector2.down, 0.1f, groundLayer);
// return raycastHit.collider != null;
// }
}
Each of the child game objects also make use of the currentHeroSpriteIndex to update their sprites, their color based on serialized fields, and their position based on the parent's:
SpritePosition.cs
using UnityEngine;
public class SpritePosition : MonoBehaviour {
[SerializeField] private string objectName;
[SerializeField] private int objectIndex;
[SerializeField] private int objectR;
[SerializeField] private int objectG;
[SerializeField] private int objectB;
private Rigidbody2D body;
private SpriteRenderer objectRenderer;
private GameObject hero;
private Rigidbody2D heroRigidBody;
private AnimationController animationControllerScript;
private int currentHeroSpriteIndex;
private HeroResources heroResourcesScript;
private Sprite[] spriteGroup;
private void Start() {
body = GetComponent<Rigidbody2D>();
objectRenderer = GetComponent<SpriteRenderer>();
hero = GameObject.Find("Hero");
heroRigidBody = hero.GetComponent<Rigidbody2D>();
animationControllerScript = hero.GetComponent<AnimationController>();
currentHeroSpriteIndex = animationControllerScript.currentHeroSpriteIndex;
heroResourcesScript = hero.GetComponent<HeroResources>();
spriteGroup = heroResourcesScript.spriteGroup[objectName][objectIndex];
float floatR = objectR / 255f;
float floatG = objectG / 255f;
float floatB = objectB / 255f;
if (objectName != "body") {
objectRenderer.color = new Color(floatR, floatG, floatB, 1);
}
}
private void Update() {
SetSprite();
SetPosition();
}
private void SetSprite() {
if (currentHeroSpriteIndex != animationControllerScript.currentHeroSpriteIndex) {
currentHeroSpriteIndex = animationControllerScript.currentHeroSpriteIndex;
objectRenderer.sprite = spriteGroup[currentHeroSpriteIndex];
}
transform.localScale = Vector3.one;
}
// for this to work, the game object must have a
// RigidBody2D component with Freeze Position active
// for X and Y axis
private void SetPosition() {
Vector2 currentHeroPosition = heroRigidBody.position;
transform.position = currentHeroPosition;
}
}
And for this, I use another script, HeroResources which loads all of the sprites on Dictionaries:
HeroResources.cs
using System.Collections.Generic;
using UnityEngine;
public class HeroResources : MonoBehaviour {
const int PANTS_LIMIT = 1;
const int BOOTS_LIMIT = 1;
const int SHIRT_LIMIT = 1;
const int TUNIC_LIMIT = 2;
const int BELT_LIMIT = 1;
public Dictionary<string, Dictionary<int, Sprite[]>> spriteGroup = new Dictionary<string, Dictionary<int, Sprite[]>>();
public Sprite[] heroSprites = new Sprite[180];
public Dictionary<int, Sprite[]> getAllSprites(string name, int limit) {
Dictionary<int, Sprite[]> spriteList = new Dictionary<int, Sprite[]>();
if (name == "hero") {
spriteList.Add(0, Resources.LoadAll<Sprite>("Spritesheets/hero/hero-body"));
} else {
for (int i = 0; i < limit; i++) {
spriteList.Add(i, Resources.LoadAll<Sprite>("Spritesheets/" + name + "/" + (i + 1)));
}
}
return spriteList;
}
void Awake() {
spriteGroup.Add("body", getAllSprites("hero", 1));
spriteGroup.Add("pants", getAllSprites("pants", PANTS_LIMIT));
spriteGroup.Add("boots", getAllSprites("boots", BOOTS_LIMIT));
spriteGroup.Add("shirt", getAllSprites("shirt", SHIRT_LIMIT));
spriteGroup.Add("tunic", getAllSprites("tunic", TUNIC_LIMIT));
spriteGroup.Add("belt", getAllSprites("belt", BELT_LIMIT));
heroSprites = Resources.LoadAll<Sprite>("Spritesheets/hero/hero-body");
}
}
So, to clarify: the Hero game object has the scripts AnimationController, HeroMovement, and HeroResources attached to it, while the child game objects only have the SpritePosition script attached.
The idea is to load the sprites, then based on the logic in the movement script, decide on the booleans to use for the animator, check which is active (currently I only have running working), then based on which one is true determine a sprite index to use. All sprites have the same name and dimensions which is why a single index works to change all. I'm basically doing it this way to avoid having hundreds of animations for each different equipment in use.
So, while the sprites update this way in sync, I'm not sure if I'm not optimizing the sprite changing (and redrawing in color) process, because when I press arrow keys to run, I get the following:
Although the screen recorder I used seems to slow down the playback, there are brief moments in which the hero's body is seen through the pants (and sometimes the boots) although the sprites are supposed to not overlap.
I'm not sure if this is because of a computer's memory, but there are 14 sprites used for running movement, which would amount to 84 sprites in total used in the span of the 2 or so seconds which the animation lasts. Is it that I'm using too much memory to load the sprites needed? Should I maybe try to find a way to merge sprites to only have a single child whose sprite gets updated? Please let me know if anyone has suggestions on how to improve my code's performance.
EDIT:
I opted to change my code to do away with the HeroResources.cs script which ultimately loads every available asset at the beginning to instead include the resource load in SpritePosition.cs so that each child game object takes care of its own spritesheet:
using UnityEngine;
public class SpritePosition : MonoBehaviour {
[SerializeField] private string objectName;
[SerializeField] private int objectIndex;
[SerializeField] private int objectR;
[SerializeField] private int objectG;
[SerializeField] private int objectB;
private Rigidbody2D body;
private SpriteRenderer objectRenderer;
private GameObject hero;
private Rigidbody2D heroRigidBody;
private AnimationController animationControllerScript;
private int currentHeroSpriteIndex;
private Sprite[] spriteGroup;
private void Start() {
body = GetComponent<Rigidbody2D>();
objectRenderer = GetComponent<SpriteRenderer>();
hero = GameObject.Find("Hero");
heroRigidBody = hero.GetComponent<Rigidbody2D>();
animationControllerScript = hero.GetComponent<AnimationController>();
currentHeroSpriteIndex = animationControllerScript.currentHeroSpriteIndex;
if (objectName != "body") {
LoadSpriteSheet();
} else {
spriteGroup = Resources.LoadAll<Sprite>("Spritesheets/hero/hero-body");
}
float floatR = objectR / 255f;
float floatG = objectG / 255f;
float floatB = objectB / 255f;
if (objectName != "body") {
objectRenderer.color = new Color(floatR, floatG, floatB, 1);
}
}
private void Update() {
SetSprite();
SetPosition();
}
private void LoadSpriteSheet() {
spriteGroup = Resources.LoadAll<Sprite>("Spritesheets/" + name + "/" + (objectIndex + 1));
}
private void SetSprite() {
if (currentHeroSpriteIndex != animationControllerScript.currentHeroSpriteIndex) {
currentHeroSpriteIndex = animationControllerScript.currentHeroSpriteIndex;
objectRenderer.sprite = spriteGroup[currentHeroSpriteIndex];
}
transform.localScale = Vector3.one;
}
private void SetPosition() {
Vector2 currentHeroPosition = heroRigidBody.position;
transform.position = currentHeroPosition;
}
}
This, however, doesn't seem to improve performance. I've checked the profiler using deep profiling, and though I see large spikes I don't see garbage coming from the SetSprite function nor much of a delay compared to EditorLoop or PlayerLoop:
I assumed there would be issues in how often the function to change sprites gets called, or how much garbage would be generated with the constant sprite changing, but it seems that this might not be the issue.
If you ever want to discover where lag or high memory usage is coming from use the unity profiler, if possible use a 3rd party profiler because unity's is kinda meh, also use deep profiling (unity will profile every method call, good to see what changes are causing lag, remember that using the profiler will reduce performance and increase memory usage as long as you have it on). There is a lot of stuff that you NEED to improve, but all of the issues I can think off will be obvious with deep profiling.
I'm trying to assign a skill to a projectile that, when used, divides the project into 3 (the original projectile and 2 more new ones).
However, when I instantiate these two clones, I cannot attribute any movement to them. The idea would be for them to take this route:
The green dotted curve indicating the motion of the original bullet, the blue vector indicating the instantaneous velocity of the original bullet at time of special activation, the red vectors indicating the two velocity vectors belonging to each of the newly spawned bullets, and the green angle indicating the direction of the new bullet relative to the original velocity direction
But at the moment, they are just standing at the point of the parabola where they were instantiated. No matter what I do, I can't seem to attach any value to their rigidbody2D. Does anyone know how to fix this?
This is my code so far
Ability Script:
public class AirSpecialSplit : MonoBehaviour, IAirSpecial
{
public float SplitAngleInDegrees = 10;
GameObject bird_down;
GameObject bird_up;
public void ExecuteAirSpecial()
{
{
//hold the velocity of the original bird
Vector2 original_velocity = this.gameObject.GetComponent<Rigidbody2D>().velocity;
//clone two new birds
bird_down = Birb.MakeBirbCopy(this.gameObject);
bird_up = Birb.MakeBirbCopy(this.gameObject);
//get the rigidboy from the clones
Rigidbody2D rb_bird_down = bird_down.GetComponent<Rigidbody2D>();
Rigidbody2D rb_bird_up = bird_up.GetComponent<Rigidbody2D>();
rb_bird_down.velocity = new Vector2(original_velocity.x, original_velocity.y) * Time.deltaTime;
rb_bird_up.AddForce(new Vector2(3, 5) * 500);
}
}
}
Main Bird:
public class Birb : MonoBehaviour
{
#region Provided Code, Do Not Edit
private Rigidbody2D m_rigidbody;
private bool m_canExecuteAirSpecial = true;
public bool CanExecuteAirSpecial
{
get
{
return m_rigidbody.simulated && m_canExecuteAirSpecial;
}
}
private void Awake()
{
m_rigidbody = GetComponent<Rigidbody2D>();
StopBirbSimulation();
}
public void StopBirbSimulation()
{
m_rigidbody.simulated = false;
}
public void StartBirbSimulation()
{
m_rigidbody.simulated = true;
}
public void SetPosition( Vector3 position )
{
if ( m_rigidbody.simulated )
{
m_rigidbody.MovePosition( position );
}
else
{
transform.position = position;
}
}
public void ExecuteAirSpecial()
{
GetComponent<IAirSpecial>().ExecuteAirSpecial();
m_canExecuteAirSpecial = false;
}
private void OnCollisionEnter2D( Collision2D collision )
{
m_canExecuteAirSpecial = false;
}
public static GameObject MakeBirbCopy( GameObject original )
{
Birb newBirb = Instantiate(original).GetComponent<Birb>();
newBirb.m_canExecuteAirSpecial = false;
return newBirb.gameObject;
}
#endregion
[Range( 0, 25 )]
public float LaunchForce = 12;
public void Launch(Vector3 offset, float maximumStretch, Rigidbody2D rigidbody)
{
rigidbody.velocity = new Vector2(offset.x * -LaunchForce, offset.y * -LaunchForce) * (maximumStretch/2);
}
}
Ok, apparently, I just need to set the "simulated" to true after instantiate.
rb_bird_down.simulated = true;
rb_bird_up.simulated = true;
The documentation could be clearer about this since they say that all attributes are copied, which is not the case...
I have a game with several levels, each level has 6 scenes, the game start directly without any menu scene, and when the player open the game he can continue from the last scene that he already reached.
I want to instantiate some elements only on game opening (like Best score, Tap to play etc...), I mean that they should be instantiated only once on the start of the game (on the level he reached).
I tried this code in GameManager but it instantiate the elements in every scene:
public GameObject PlayButton;
bool GameHasEnded = false;
public float RestartDelay = 2f;
public float NextLevelDelay = 5f;
public int level_index;
private static bool loaded = false;
private void Start()
{
if (!loaded)
{
loaded = true;
level_index = PlayerPrefs.GetInt("Last_Level");
SceneManager.LoadScene(level_index);
}
GameObject canvas = GameObject.Find("Canvas");
GameObject play = Instantiate(PlayButton, canvas.transform.position, Quaternion.identity);
play.transform.SetParent(canvas.transform, false);
}
public void CompleteLevel()
{
Invoke("NextLevel", NextLevelDelay);
}
public void EndGame()
{
if (GameHasEnded == false)
{
GameHasEnded = true;
Invoke("Restart", RestartDelay);
}
}
void NextLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex +1);
level_index = SceneManager.GetActiveScene().buildIndex + 1;
PlayerPrefs.SetInt("Last_Level", level_index);
PlayerPrefs.Save();
}
void Restart()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().path);
}
You already have an if block there with the static flag loaded
Since you there load another scene you need a similar second flag e.g.
private static bool loadedPrefab = false;
private void Start()
{
if (!loaded)
{
loaded = true;
level_index = PlayerPrefs.GetInt("Last_Level");
SceneManager.LoadScene(level_index);
// Return because you don't want to execute the rest yet
// but instead in the new loaded scene
return;
}
// The same way skip if the prefab was already loaded before
if(!loadedPrefab)
{
loadedPrefab = true;
GameObject canvas = GameObject.Find("Canvas");
GameObject play = Instantiate(PlayButton, canvas.transform.position, Quaternion.identity);
play.transform.SetParent(canvas.transform, false);
}
}