How to update sprite position faster? - unity3d

For the game I'm trying to do, I have quite a few objects:
Where all start with a specific sprite, like the Hero:
Except that the Hero is the only one which has an Animator component and follows animation states.
The other game objects would get their sprite and position based on the Hero, using the following script:
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 SpriteRenderer heroRenderer;
private Sprite currentHeroSprite;
private HeroResources heroResourcesScript;
private HeroMovement heroMovementScript;
private Sprite[] spriteGroup;
private void Start() {
body = GetComponent<Rigidbody2D>();
objectRenderer = GetComponent<SpriteRenderer>();
hero = GameObject.Find("Hero");
heroRigidBody = hero.GetComponent<Rigidbody2D>();
heroRenderer = hero.GetComponent<SpriteRenderer>();
currentHeroSprite = heroRenderer.sprite;
heroResourcesScript = hero.GetComponent<HeroResources>();
Debug.Log(objectName + "(" + objectR + ", " + objectG + ", " + objectB + ")");
spriteGroup = heroResourcesScript.spriteGroup[objectName];
}
private void Update() {
heroMovementScript = hero.GetComponent<HeroMovement>();
if (currentHeroSprite != heroRenderer.sprite) {
currentHeroSprite = heroRenderer.sprite;
}
SetSprite();
SetPosition();
}
private bool shouldMirrorSprite(int index) {
return index >= 0 && index <= 34 ||
index >= 69 && index <= 71 ||
index >= 81 && index <= 83 ||
index >= 97 && index <= 99 ||
index >= 117 && index <= 118 ||
index >= 133 && index <= 175;
}
private void SetSprite() {
int currentSpriteIndex = int.Parse(currentHeroSprite.name.Replace("hero-body_", ""));
objectRenderer.sprite = spriteGroup[currentSpriteIndex];
objectRenderer.color = new Color32((byte)objectR, (byte)objectG, (byte)objectB, 255);
if (heroMovementScript.isFacingLeft && shouldMirrorSprite(currentSpriteIndex)) {
transform.localScale = new Vector3(-1, 1, 1);
} else {
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;
}
}
Which, when added as a component, expects a name, index, and R, G, and B values:
Here, the Name is necessary to load specific sprites for each game object, in this script:
using System.Collections.Generic;
using UnityEngine;
public class HeroResources : MonoBehaviour
{
public Dictionary<string, Sprite[]> spriteGroup = new Dictionary<string, Sprite[]>();
void Awake () {
spriteGroup.Add("pants", Resources.LoadAll<Sprite>("Spritesheets/pants"));
spriteGroup.Add("boots", Resources.LoadAll<Sprite>("Spritesheets/boots"));
spriteGroup.Add("shirt", Resources.LoadAll<Sprite>("Spritesheets/shirt"));
spriteGroup.Add("tunic", Resources.LoadAll<Sprite>("Spritesheets/tunic"));
spriteGroup.Add("belt", Resources.LoadAll<Sprite>("Spritesheets/belt"));
Debug.Log(spriteGroup.Count);
}
}
and the sprites are loaded from several folders in the Resources folder:
These spritesheets are all of the same size, so they can be cleanly sliced:
Thus, having the sprites like this, I can simply call the SetSprite function and the SetPosition function based on the Hero:
private void SetSprite() {
int currentSpriteIndex = int.Parse(currentHeroSprite.name.Replace("hero-body_", ""));
objectRenderer.sprite = spriteGroup[currentSpriteIndex];
objectRenderer.color = new Color32((byte)objectR, (byte)objectG, (byte)objectB, 255);
if (heroMovementScript.isFacingLeft && shouldMirrorSprite(currentSpriteIndex)) {
transform.localScale = new Vector3(-1, 1, 1);
} else {
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;
}
This works, and the other sprites update based on the index of the Hero game object and follow it, so it looks like the user has a lot of equipment. However, sometimes the sprites either fail to keep up or the position falls behind a bit:
There also seems to be some black lines toward the top of the sprites. I assume this happens because the sprites do not load fast enough, and if so, is there a way to ensure these sprites load faster? Or does this have to do with a computer's performance?
Also, to have the sprites follow, I need to freeze X and Y position on the other game objects' RigidBody2D. The Hero has Transform, Sprite Renderer, Box Collider 2D, RigidBody2D, two scripts (HeroMovement and HeroResources), and Animator components, while the other game objects only have Transform, Sprite Renderer, RigidBody2D, and a script (SpritePosition)

Instead of positioning the clothes yourself you could let Transform component do the heavy lifting by just placing the cloth GameObjects under the hero GameObject with correct offsets.
This way you could also have HeroAnimator component that gets all the clothing components under the hero with GetComponentsInChildren at startup and changes the sprites and flip them to match movement direction. Clothing components could focus
because the sprites do not load fast enough
You're loading all the sprite resources with HeroResources.Awake so they should all be loaded to memory during startup. Same goes for all sprites you're referencing in your scene.
String operations like this in update method can generate surprising amount of garbage for the garbage collector which can affect performance down the line.
int currentSpriteIndex = int.Parse(currentHeroSprite.name.Replace("hero-body_", ""));
Its good to note that implementing something like this can get really tricky. Many 2D games that let you change outfit use skeletal animation or have very limited set of minimal animations.

Related

Sibling sprite doesn't appear to follow main GameObject sprite even when transform.position updates

Following this question How to align sprites of smaller sizes to a moving gameobject sprite?, I'm trying to redraw my sprites to avoid rendering transparent pixels and try to maximize performance, and as I try starting this process with the sword sprites, I need to change the way the Sword game object follows the Player, which before was handled simply by using transform.position = hero.transform.position; because since both objects use sprites that are perfect squares they would have the same pivot point despite their size difference.
At first, it seemed to me the problem lied in how the pivots are different, thus I would need to update the transform position of the Sword every time its attack animation changes sprites. For that, I made a function which updates variables that influence the position as that gets updated:
here, heroTransform is a variable which gets sent the Player's transform property on the script's Start function as heroTransform = hero.transform;, where hero is defined right above it as hero = GameObject.Find("Hero");.
I would have expected this to make the Sword which is equipped with this script, called WieldablePosition to follow the player position, but it seems stuck in the same position as it starts:
I'm not sure, but I don't think I changed anything that would stop the Sword from moving. In what cases could a GameObject remain in a single place even as the transform.position is modified? For reference, please view the script:
using UnityEngine;
public class WieldablePosition : MonoBehaviour {
GameObject hero;
HeroMovement heroMovementScript;
private Transform heroTransform;
protected Animator anim;
private bool isAirAttackSingle;
private bool isFacingLeft;
private float x = 0;
private float y = 0;
void Start() {
hero = GameObject.Find("Hero");
heroTransform = hero.transform;
heroMovementScript = hero.GetComponent<HeroMovement>();
anim = GetComponent<Animator>();
isAirAttackSingle = heroMovementScript.isAirAttackSingle;
isFacingLeft = heroMovementScript.isFacingLeft;
}
void Update() {
UpdatePosition();
isAirAttackSingle = heroMovementScript.isAirAttackSingle;
isFacingLeft = heroMovementScript.isFacingLeft;
anim.SetBool("isAirAttackSingle", isAirAttackSingle);
if (isFacingLeft) {
transform.localScale = new Vector3(-1, 1, 1);
} else {
transform.localScale = Vector3.one;
}
}
public void SetPosition(AnimationEvent eventParams) {
string[] eventPositions = eventParams.stringParameter.Split(',');
x = float.Parse(eventPositions[0]);
y = float.Parse(eventPositions[1]);
}
private void UpdatePosition() {
transform.position = new Vector2(heroTransform.position.x + x, heroTransform.position.y + y);
}
}

Unity - How to calculate new position for an object from pitch of another object in 3D

I would like to calculate a new position based on the pitch of a mesh in order to make an object following the top of my object which is rotated:
And result in:
I cannot make the square object as represented above as a child (in the Unity object hierarchy) of the line object because the rotated object can see its scale changed at anytime.
Does a mathematics solution can be used in this case?
Hotspots
If you'd like to place something at a particular location on a generic object which can be scaled or transformed anywhere, then a "hotspot" can be particularly useful.
What's a hotspot?
Edit the target gameobject (the line in this case) and add an empty gameobject to it. Give it some appropriate name - "cross arms hotspot" for example, and then move it to the location where you'd like your other gameobject to target. Essentially, a hotspot is just an empty gameobject - a placement marker of sorts.
How do I use it?
All you need is a reference to the hotspot gameobject. You could do this by adding a little script to the pole gameobject which tracks it for you:
public class PowerPole : MonoBehaviour {
public GameObject CrossArmsHotspot; // Set this in the inspector
}
Then you can get that hotspot reference from any power pole instance like this:
var targetHotspot = aPowerPoleGameObject.GetComponent<PowerPole>().CrossArmsHotspot;
Then it's just a case of getting your target object to place itself where that hotspot is, using whichever technique you prefer. If you want it to just "stick" there, then:
void Start(){
targetHotspot = aPowerPoleGameObject.GetComponent<PowerPole>().CrossArmsHotspot;
}
void Update(){
transform.position = targetHotspot.transform.position;
}
would be a (simplfied) example.
A more advanced example using lerp to move towards the hotspot:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CrossArmsMover : MonoBehaviour
{
public GameObject PowerPole;
private GameObject targetHotspot;
public GameObject CrossArms;
public float TimeToTake = 5f;
private float timeSoFar;
private Vector3 startPosition;
private Quaternion startRotation;
// Start is called before the first frame update
void Start()
{
startPosition = CrossArms.transform.position;
startRotation = CrossArms.transform.rotation;
targetHotspot = PowerPole.GetComponent<PowerPole>().CrossArmsHotspot;
}
// Update is called once per frame
void Update()
{
timeSoFar+=Time.deltaTime;
var progress = timeSoFar/TimeToTake;
// Clamp it so it doesn't go above 1.
if(progress > 1f){
progress = 1f;
}
// Target position / rotation is..
var targetPosition = targetHotspot.transform.position;
var targetRotation = targetHotspot.transform.rotation;
// Lerp towards that target transform:
CrossArms.transform.position = Vector3.Lerp(startPosition, targetPosition, progress);
CrossArms.transform.rotation = Quaternion.Lerp(startRotation, targetRotation, progress);
}
}
You would need to put a script on the following gameobject in wich you would put :
GameObject pitcher = //reference to the gameobject with the pitch;
const int DISTANCE_ON_LINE = //distance between the 2 objects
void Update() {
transform.position = pitcher.transform.position + pitcher.transform.forward * DISTANCE_ON_LINE;
}

Ball falls through platform collider

There are 2 GameObjects; a platform and a ball.
The ball is controlled via a custom controller, and the platform moves via an animation.
Components
platform
+ rigidbody
+ box collider
ball
+ rigidbody
+ sphere collider
When the ball comes in contact with the platform, the ball should stop its current velocity and attain the velocity of the platform it is in contact with. However currently, the ball just falls straight through the platform as if there is no colliders attached.
Code of Player:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class PlayerController : MonoBehaviour {
public Text winText;
public float speed;
public Text countText;
public GameObject light;
public GameObject player;
private Rigidbody rb;
private int count;
private int a = 0;
private int b = 0;
private int c = 0;
void Start ()
{
rb = GetComponent<Rigidbody>();
count = 0;
SetCountText ();
winText.text = "";
}
void FixedUpdate()
{
if (player.transform.position.y < -15) {
transform.position = new Vector3(a, b, c);
}
float moveHorizontal = Input.GetAxis ("Horizontal");
float moveVertical = Input.GetAxis ("Vertical");
Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);
rb.AddForce (movement * speed );
}
void OnTriggerEnter (Collider other)
{
if (other.gameObject.CompareTag ("Pick Up"))
{
other.gameObject.SetActive (false);
count = count + 1;
SetCountText ();
}
if (other.gameObject.CompareTag ("Check point"))
{
other.gameObject.SetActive (false);
light.gameObject.SetActive (false);
a = 0;
b = -10;
c = 96;
}
}
void SetCountText ()
{
countText.text = "Score: " + count.ToString();
if (count >= 8)
{
winText.text = "You Win!";
}
}
}
You said you are using a custom controller. Please make sure that you are not using Transform() to change the ball's position manually and move your ball as this defies the physics laws in unity. Instead use Rigidbody.MovePosition().
More at Unity docs
Make sure you have all those game objects in same Z axis.
Throw a debug.log() message in your OnCollisionEnter2D() method to see if they are actually colliding.
Could you also check for the type of Colliders you are using.
More Details about Collision in unity :
https://docs.unity3d.com/Manual/CollidersOverview.html
Also if it a custom controller make sure that something is not changing the position of ball to go below the platform.
Fast moving objects need the dynamic ContinuousDynamic or Continuous mode to work reliable
Make sure both rigidbodies are kinetic and the colliders are not triggers. Thats what really worked for me.

How to change Polygon Collider based on my animation in Unity3d?

I have a small car game, and when I move up/down and left/right, the sprite becomes different. But the physicsbody remains the same. How do I adjust physicsbody? I added a screenshot of my sprite. At the moment I have Polygon physics body as on the right one.
Here is the code that adjusts animation states:
void FixedUpdate()
{
if (Input.GetKey(KeyCode.W)) {
rb2d.AddForce(Vector2.up * physicsConstant);
animator.CrossFade("CarUpIdle", 0);
} else if (Input.GetKey(KeyCode.S)) {
rb2d.AddForce(-Vector2.up * physicsConstant);
animator.CrossFade("CarDownIdle", 0);
} else if (Input.GetKey(KeyCode.D)) {
rb2d.AddForce(Vector2.right * physicsConstant);
animator.CrossFade("CarRightIdle", 0);
} else if (Input.GetKey(KeyCode.A)) {
rb2d.AddForce(-Vector2.right * physicsConstant);
animator.CrossFade("CarLeftIdle", 0);
}
}
To Change polygon based on sprite first you will need to have Serializefield variables to keep track of all the respective colliders. In this script basically what I do is I am keeping all the polygon colliders in array and iterate the array and enable it depending upon the sprite.
In the script im putting the required sprites along with the respective collider for sprite in sequence. So when I request the sprite to change I enable the respective collider and disables the other colliders. You will require something similer like this :
[SerializeField]
private Sprite[] Sprites;
[SerializeField]
private PolygonCollider2D[] Colliders;
private int index = 0;
private SpriteRenderer sp;
void Start () {
sp = GetComponent<SpriteRenderer>();
sp.sprite = Value[index];
}
void OnGUI() {
if(GUI.Button(new Rect(0,0, 80,35), "ChangeSprite")) {
colliders[index].enabled = false;
index ++;
if(index > Value.Length -1) {
index = 0;
}
sp.sprite = Sprites[index];
colliders[index].enabled = true;
}
}
Also in this tutorial it has been explained how to tackle this kind of problem
Unity Game Tutorial
Another way to proceed is to remove the ploygon collider and recreate it
Destroy(GetComponent<PolygonCollider2D>());
gameObject.AddComponent<PolygonCollider2D>();
though this is very bad programming considering I will be creating collider on every sprite change which is heavy on game.

player won't stick on a moving platform

I have created some moving platforms for my game. There's a game object called PlatformPath1 which has children that define the start and end of the platform's cycle and then obviously there's the actual platform which follows the path. The platform moves perfect however, like expected, the player does not move with the platform. How would I get the player to move with the platform?
Here is the code for PlatformPath1
using System.Collections.Generic;
using UnityEngine;
using System.Collections;
public class FollowPath : MonoBehaviour
{
public enum FollowType
{
MoveTowards,
Lerp
}
public FollowType Type = FollowType.MoveTowards;
public PathDefinition Path;
public float Speed = 1;
public float MaxDistanceToGoal = .1f;
private IEnumerator<Transform> _currentPoint;
public void Start()
{
if (Path == null)
{
Debug.LogError("Path cannot be null", gameObject);
return;
}
_currentPoint = Path.GetPathEnumerator();
_currentPoint.MoveNext();
if (_currentPoint.Current == null)
return;
transform.position = _currentPoint.Current.position;
}
public void Update()
{
if (_currentPoint == null || _currentPoint.Current == null)
return;
if (Type == FollowType.MoveTowards)
transform.position = Vector3.MoveTowards (transform.position, _currentPoint.Current.position, Time.deltaTime * Speed);
else if (Type == FollowType.Lerp)
transform.position = Vector3.Lerp(transform.position, _currentPoint.Current.position, Time.deltaTime * Speed);
var distanceSquared = (transform.position - _currentPoint.Current.position).sqrMagnitude;
if (distanceSquared < MaxDistanceToGoal * MaxDistanceToGoal)
_currentPoint.MoveNext();
}
}
And here is the code for the platform
using System;
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
public class PathDefinition : MonoBehaviour
{
public Transform[] Points;
public IEnumerator<Transform> GetPathEnumerator()
{
if(Points == null || Points.Length < 1)
yield break;
var direction = 1;
var index = 0;
while (true)
{
yield return Points[index];
if (Points.Length == 1)
continue;
if (index <= 0)
direction = 1;
else if (index >= Points.Length - 1)
direction = -1;
index = index + direction;
}
}
public void OnDrawGizmos()
{
if (Points == null || Points.Length < 2)
return;
for (var i = 1; i < Points.Length; i++) {
Gizmos.DrawLine (Points [i - 1].position, Points [i].position);
}
}
}
You could add the Player as a child to the platform. That way, when the platform moves, the player will too.
At the end of the movement (or on some user input), you could break the parenting. Here's some code you can try out.
public GameObject platform; //The platform Game Object
public GameObject player; //The player Game Object
//Call this to have the player parented to the platform.
//So, say for example, you have a trigger that the player steps on
//to activate the platform, you can call this method then
void AttachPlayerToPlatform () {
player.transform.parent = platform.transform;
}
//Use this method alternatively, if you wish to specify which
//platform the player Game Object needs to attach to (more useful)
void AttachPlayerToPlatform (GameObject platformToAttachTo) {
player.transform.parent = platformToAttachTo.transform;
}
//Call this to detach the player from the platform
//This can be used say at the end of the platform's movement
void DetachPlayerFromPlatform () {
player.transform.parent = null;
}
If you were to stand on a moving platform in the real world, the reason you would move with the platform is because of the friction between the platform and your feet. If the platform were covered in ice, you might not move with the platform if it begins its motion while you're on it!
Now we can implement this in your game's physics. Normally your friction might just slow your player down to zero speed when you're not trying to walk. Instead you can check the ground collider on impact to see if it has a moving platform script, and get the speed of the platform. Then use this speed as the "zero point" for your motion calculation.
I thought this was going to be difficult, but I actually found it really simple to make a solution that seems to work pretty well.
The character in my game has a Box Collider. It's not a trigger and it doesn't use a physics material. It has a Rigidbody with a Mass of 1, Drag of 0, Angular Drag of 0.05, Uses Gravity, is not Kinematic, does not Interpolate, and Collision Detection is Discrete. It has No Constraints. For lateral movement, I use transform.Translate and for jumping I use Rigidbody's AddForce with ForceMode.VelocityChange as the final argument.
The moving platform also has a Box Collider. I set the transform.position based on the results of a call to Vector3.Lerp on every frame. I also have this script on the moving platform:
void OnCollisionEnter(Collision collision) {
// Attach the object to this platform, but have them stay where they are in world space.
collision.transform.SetParent(transform, true);
Debug.Log("On platform.");
}
void OnCollisionExit(Collision collision) {
// Detach the object from this platform, but have them stay where they are in world space.
collision.transform.SetParent(transform.parent, true);
Debug.Log("Off platform.");
}
And that's all. Really, it's just two lines of code in the script to declare that I'm implementing the two methods, and then the actual implementation of each is a single line. All of the info at the start is just for in case the Collision Detection isn't actually working out in your project (I always forget what the rules are for what does and doesn't count as a collision and/or a trigger.)