I am using XNA to make a tank game. I've implemented a feature to shoot bullets using a list. After shooting, I want to test if the bullet has gotten close to the boundaries of the screen. If so, remove that particular bullet from the list.
The error appears only when I have more than one bullet on the screen at any given time. Here is the code:
Tank Class:
List<Bullet> bulletList = new List<Bullet>();
bool spacebarPrepared = true; //for shooting every 0.5 seconds
short count = 0;
//Shoot
if (Keyboard.GetState().IsKeyDown(Keys.Space) && spacebarPrepared == true)
{
bulletList.Add(new Bullet(sprBullet, position, turretDirection, turretAngle));
spacebarPrepared = false;
}
if (spacebarPrepared == false)
{
spacebarCount += (float)gameTime.ElapsedGameTime.TotalSeconds;
if (spacebarCount > 0.5)
{
spacebarPrepared = true;
spacebarCount = 0;
}
}
//Update bullets
foreach (Bullet bullet in bulletList)
{
bullet.Update(bounds);
}
count = (short)bulletList.Count;
//Remove unwanted bullets
for (short i = 0; i < count; i++)
{
if (bulletList[i].Alive == false)
{
bulletList.Remove(bulletList[i]);
}
}
Bullet Class:
class Bullet
{
Texture2D spr;
Vector2 origin, pos, dir, turretLength;
float rotation, scale, turretLeng;
short speed;
bool alive = true;
public Bullet(Texture2D sprite, Vector2 position, Vector2 direction, float angle)
{
spr = sprite;
scale = 0.15f;
turretLeng = (110 + spr.Width) * scale;
speed = 5;
rotation = angle;
pos = position;
dir = direction;
origin = new Vector2(spr.Width / 2, spr.Height / 2);
FindTurretLength();
}
public void Draw(SpriteBatch spriteBatch)
{
Matrix bulletTranslation = Matrix.CreateRotationZ(rotation) * Matrix.CreateTranslation(pos.X + turretLength.X, pos.Y + turretLength.Y, 0);
spriteBatch.Begin(SpriteSortMode.BackToFront, null, null, null, null, null, bulletTranslation);
spriteBatch.Draw(spr, Vector2.Zero, null, Color.White, 0, origin, 0.15f, SpriteEffects.None, 1f);
spriteBatch.End();
}
public void Update(Vector2 boundary)
{
pos += dir * speed;
if (pos.X < 50 || pos.X > boundary.X - 50 || pos.Y < 50 || pos.Y > boundary.Y - 50)
{
alive = false;
}
}
public void FindTurretLength()
{
turretLength = new Vector2(turretLeng * dir.X, turretLeng * dir.Y);
}
public Vector2 Pos
{
get
{
return pos;
}
set
{
pos = value;
}
}
public bool Alive
{
get
{
return alive;
}
set
{
alive = value;
}
}
}
What I noticed myself when debugging was that my own 'count' variable = 2, yet the bulletList.Count = 1. Could that be the problem? How is that occurring?
Any help is greatly appreciated.
The problem is in your for loop that removes bullets.
Lets say you have a list of 10 bullets (indexes 0 - 9) when you start the loop. The 1st bullet (at index 0) gets removed....now your list has 9 bullets (indexes 0 - 8), but the count variable has not been updated so your for loop still thinks it has 10.
When you reach the point where "i" is greater than the ACTUAL amount of bullets alive, you'll get the "Index was out of range." error.
There are multiple ways you can fix this error.
I would go for:
bulletList.RemoveAll(x => !x.Alive);
Related
So decided to give game dev a try, picked up unity,Now I decided to create a simple ping pong game.
My game has Bat.cs class, ball.cs, and GameHandler.cs.
The GameHandler.cs initializes the batRight, batLeft, ball using the Bat.cs, Ball.cs class and prefabs, it also keeps track of if someone scores then starts a serve by stopping the rally:
public class GameHandler : MonoBehaviour
{
public Bat bat;
public Ball ball;
public Score score;
public static Vector2 bottomLeft;
public static Vector2 topRight;
public static bool rallyOn = false;
public static bool isServingLeft = true;
public static bool isServingRight = false;
public static bool isTwoPlayers = true;
static Bat batRight;
static Bat batLeft;
public static Ball ball1;
static Score scoreLeft;
static Score scoreRight;
// Start is called before the first frame update
void Start()
{
//Convert screens pixels coordinate into games coordinate
bottomLeft = Camera.main.ScreenToWorldPoint(new Vector2(0, 0));
topRight = Camera.main.ScreenToWorldPoint(new Vector2(Screen.width, Screen.height));
ball1 = Instantiate(ball) as Ball;
//Debug.Log("GameHandler.Start");
batRight = Instantiate(bat) as Bat;
batRight.Init(true);
batLeft = Instantiate(bat) as Bat;
batLeft.Init(false);
ball1.transform.position = new Vector3(batLeft.transform.position.x + ball1.getRadius() * 2, batLeft.transform.position.y);
//Instatiate scores
scoreLeft = Instantiate(score, new Vector2(-2, -4), Quaternion.identity) as Score;
scoreRight = Instantiate(score, new Vector2(2, -4), Quaternion.identity) as Score;
}
private void Update()
{
if (isServingLeft)
{
ball1.transform.position = new Vector3(batLeft.transform.position.x + ball1.getRadius() * 2, batLeft.transform.position.y);
if (Input.GetKey(KeyCode.LeftControl))
{
rallyOn = true;
isServingLeft = false;
}
}
if (isServingRight)
{
ball1.transform.position = new Vector3(batRight.transform.position.x - ball1.getRadius() * 2, batRight.transform.position.y);
if (isTwoPlayers && Input.GetKey(KeyCode.RightControl))
{
rallyOn = true;
isServingRight = false;
}
else
{
StartCoroutine(batRight.serveAi());
if (GameHandler.rallyOn)
{
StopCoroutine(batRight.serveAi());
}
}
}
}
public static void increaseScoreByOne(bool isRight)
{
rallyOn = false;
if (isRight)
{
scoreRight.increaseScore();
isServingRight = true;
}
else
{
scoreLeft.increaseScore();
isServingLeft = true;
}
}
}
The ball.cs listens to the static GameHandler.rallyOn and starts moving the ball accordingly, it also changes direction of the ball if it hits a vertical wall or the bat:
public class Ball : MonoBehaviour
{
[SerializeField]
float speed;
float radius;
public Vector2 direction;
public Vector3 ballPosition;
// Start is called before the first frame update
void Start()
{
direction = Vector2.one.normalized; // direction is (1, 1) normalized
//Debug.Log("direction * speed * Time.deltaTime:" + direction * speed * Time.deltaTime);
radius = transform.localScale.x / 2;
}
// Update is called once per frame
void Update()
{
ballPosition = transform.position;
if (GameHandler.rallyOn)
{
startRally();
}
}
void startRally()
{
transform.Translate(direction * speed * Time.deltaTime);
Debug.Log("direction * speed * Time.deltaTime:" + direction * speed * Time.deltaTime);
if ((transform.position.y + radius) > GameHandler.topRight.y && direction.y > 0)
{
direction.y = -direction.y;
}
if ((transform.position.y + radius) < GameHandler.bottomLeft.y && direction.y < 0)
{
direction.y = -direction.y;
}
if ((transform.position.x + radius) > GameHandler.topRight.x && direction.x > 0)
{
// Left player scores
GameHandler.increaseScoreByOne(false);
//For no, just freeza the script
// Time.timeScale = 0;
//enabled = false;
}
if ((transform.position.x - radius) < GameHandler.bottomLeft.x && direction.x < 0)
{
// right player scores
GameHandler.increaseScoreByOne(true);
}
}
void OnTriggerEnter2D(Collider2D collision)
{
if(collision.tag == "Bat")
{
if (collision.GetComponent<Bat>().isRight && direction.x > 0)
{
direction.x = -direction.x;
}
if (!collision.GetComponent<Bat>().isRight && direction.x < 0)
{
direction.x = -direction.x;
}
}
}
public float getRadius()
{
return radius;
}
}
The bat.cs initiazes the two paddles, and either listens to user input if its 2 players or listens to player and use AI if it is player vs CPU.:
public class Bat : MonoBehaviour
{
float height;
[SerializeField] // this make this appear on editor without making this field public
float speed;
string input;
public bool isRight;
string PLAYER1_INPUT = "PaddleLeft";
string PLAYER2_INPUT = "PaddleRight";
// Start is called before the first frame update
void Start()
{
height = transform.localScale.y;
}
// Update is called once per frame
void Update()
{
if (GameHandler.isTwoPlayers)
{
if (isRight)
{
movePaddleonUserInput(PLAYER2_INPUT);
}
else
{
movePaddleonUserInput(PLAYER1_INPUT);
}
}
else
{
if (isRight)
{
movePaddleAi();
}
else
{
movePaddleonUserInput(PLAYER1_INPUT);
}
}
}
void movePaddleAi()
{
if (isRight && GameHandler.ball1.direction.x > 0 && GameHandler.ball1.ballPosition.x > 0)
{
//transform.Translate(direction * speed * Time.deltaTime);
if ((transform.position.y) > GameHandler.ball1.ballPosition.y && GameHandler.ball1.direction.y < 0)
{
transform.Translate(GameHandler.ball1.direction * speed * Time.deltaTime * Vector2.up);
}
if ((transform.position.y) < GameHandler.ball1.ballPosition.y && GameHandler.ball1.direction.y > 0)
{
transform.Translate(GameHandler.ball1.direction * speed * Time.deltaTime * Vector2.up);
}
}
}
void movePaddleonUserInput(string input)
{
// move = (-1 -> 1) * speed * timeDelta(this keep move framerate independent)
float move = Input.GetAxis(input) * speed * Time.deltaTime;
//Debug.Log((transform.position.y + height / 2) + " > "+ GameHandler.topRight.y+ "&&" + move +" > 0");
//Restrict paddle movement to to the screen
// (top edge of paddle > Screen top and we are moving up)
if ((transform.position.y + height / 2) > GameHandler.topRight.y && move > 0)
{
move = 0;
}
// (bottom edge of paddle < Screen top and we are moving down)
if ((transform.position.y - height / 2) < GameHandler.bottomLeft.y && move < 0)
{
move = 0;
}
transform.Translate(move * Vector2.up);
}
public void Init(bool isRightPaddle)
{
isRight = isRightPaddle;
Vector2 pos;
if (isRightPaddle)
{
isRight = isRightPaddle;
pos = new Vector2(GameHandler.topRight.x, 0);
// offset since center is the anchor
pos -= Vector2.right * transform.localScale.x;
input = "PaddleRight";
}
else
{
pos = new Vector2(GameHandler.bottomLeft.x, 0);
// offset since center is the anchor
pos += Vector2.right * transform.localScale.x;
input = "PaddleLeft";
}
transform.name = input;
transform.position = pos;
}
public IEnumerator serveAi()
{
yield return new WaitForSecondsRealtime (2f);
GameHandler.rallyOn = true;
GameHandler.isServingRight = false;
}
}
Now I also have score.cs and mainMenu scene , but no need to include that for this question, What I am struggling with now is the AI not stopping after it scores a point, I want the AI paddle to stop for a few seconds before it serves.
As you can see in the code, I added a yield return new WaitForSecondsRealtime(2f);
public IEnumerator serveAi()
{
yield return new WaitForSecondsRealtime (2f);
GameHandler.rallyOn = true;
GameHandler.isServingRight = false;
}
But this only cause the paddle to wait for the first time it scores , and then if it scores again in quick interval, it doesn't wait at all.
Not sure what I doing wrong here, thanks for your time.
The more plausible explanation to me would be that GameHandler.rallyOn and isServing* aren't all being reset to the correct values to prevent the serve from occurring before the coroutine even begins, or another check is needed somewhere to prevent a serve from occurring if all of the correct booleans aren't set. Try placing appropriate breakpoints and stepping through in a debugger.
You'd probably be better off using WaitForSeconds rather than WaitForSecondsRealtime, by the way, at least if there's a chance you might want to allow the game to be paused in the future.
Realized that the coroutine is called multiple times even before its finished due do it being called from update function in GameHandler.cs. Needed a way to check if the co-routine is already running and not start a new one from update if so, used the Game.isServing variable to do so:
public IEnumerator serveAi()
{
GameHandler.isServingRight = false; // coroutine started , no need to start new one
yield return new WaitForSecondsRealtime (2f);
GameHandler.rallyOn = true;
}
And my update in GameHandler is already checking for that variable before starting the coroutine:
private void Update()
{
if (isServingLeft)
{
ball1.transform.position = new Vector3(batLeft.transform.position.x + ball1.getRadius() * 2, batLeft.transform.position.y);
if (Input.GetKey(KeyCode.LeftControl))
{
rallyOn = true;
isServingLeft = false;
}
}
if (isServingRight)
{
ball1.transform.position = new Vector3(batRight.transform.position.x - ball1.getRadius() * 2, batRight.transform.position.y);
if (isTwoPlayers && Input.GetKey(KeyCode.RightControl))
{
rallyOn = true;
isServingRight = false;
}
else
{
StartCoroutine(batRight.serveAi());
}
}
}
I created an editor tool that creates game objects in a circle, equally apart from each other (code below). It has a create button, so parameters are adjusted before the create is smashed. I would like to see changes made dynamically in the scene view rather. How can I adjust the following parameters with a slider rather:
Create or destroy game objects using an object count slider, whiles adjusting to keep the objects the same angle apart
dynamically adjust the game objects by changing a radius slider
rotate all objects on the circle (like spinning a wheel) using an angle slider
CircleSpawn
public class CircleSpawn : MonoBehaviour {
public float radius;
public int numOfItems;
public GameObject clonedObject;
public List<GameObject> spawnedObjects;
}
CircleSpawnEditor
[CustomEditor(typeof(CircleSpawn))]
public class CircleSpawnEditor : Editor
{
public override void OnInspectorGUI()
{
var tar = (CircleSpawn)target;
//set its values
tar.radius = EditorGUILayout.FloatField("Radius:", tar.radius);
tar.numOfItems = EditorGUILayout.IntField("Number of Items:", tar.numOfItems);
tar.clonedObject = (GameObject)EditorGUILayout.ObjectField(tar.clonedObject,
typeof(GameObject), true);
//Inspector button for creating the objects in the Editor
if (GUILayout.Button("Create"))
{
//clean up old objects
if (tar.spawnedObjects != null)
{
foreach (var ob in tar.spawnedObjects)
{
DestroyImmediate(ob);
}
}
tar.spawnedObjects = new List<GameObject>();
float angleBetween = 360.0f / tar.numOfItems;
float angle = 0;
for (int i = 0; i <= tar.numOfItems; i++)
{
//for each object, find a rotation and position
var rot = Quaternion.Euler(0, 0, angle);
var localPos = rot * Vector3.right * tar.radius;
tar.spawnedObjects.Add(Instantiate(tar.clonedObject,
tar.transform.position + localPos, rot));
angle += angleBetween;
}
}
}
}
Create or destroy game objects using an object count slider, whiles adjusting to keep the objects the same angle apart
Use an IntSlider for numOfItems and re-create the objects when:
numOfItems != spawnedObjects.Count
Dynamically adjust the game objects by changing a radius slider
Use a Slider for radius, and when it changes, iterate over spawnedObjects and move them by:
pos = rot * Vector3.right * tar.radius
Rotate all objects on the circle (like spinning a wheel) using an angle slider
Use a Slider for spin, and when it changes, iterate over spawnedObjects and rotate them by:
rot = Quaternion.Euler(0, 0, tar.spin + angle)
CircleSpawn:
public class CircleSpawn : MonoBehaviour
{
public float radius, radiusLast, spin, spinLast;
public int numOfItems;
public GameObject clonedObject;
public List<GameObject> spawnedObjects;
}
CircleSpawnEditor:
[CustomEditor(typeof(CircleSpawn))]
public class CircleSpawnEditor : Editor
{
public override void OnInspectorGUI ()
{
var tar = (CircleSpawn)target;
EditorGUILayout.LabelField("Radius"); // Set as required
tar.radius = EditorGUILayout.Slider(tar.radius, 0f, 100f);
EditorGUILayout.LabelField("Angle"); // Set as required
tar.spin = EditorGUILayout.Slider(tar.spin, 0f, 360f);
EditorGUILayout.LabelField("Number of Items"); // Set as required
tar.numOfItems = EditorGUILayout.IntSlider(tar.numOfItems, 0, 36);
EditorGUILayout.LabelField("Object");
tar.clonedObject = (GameObject)EditorGUILayout.ObjectField(tar.clonedObject,
typeof(GameObject), true);
float angle, angleBetween = 360.0f / tar.numOfItems;
if (tar.spawnedObjects == null)
tar.spawnedObjects = new List<GameObject>();
// Solution #1
if (tar.spawnedObjects.Count != tar.numOfItems)
{
foreach (var ob in tar.spawnedObjects)
DestroyImmediate(ob);
tar.spawnedObjects.Clear();
angle = 0f;
for (int i = 0; i < tar.numOfItems; i++)
{
var rot = Quaternion.Euler(0f, 0f, tar.spin + angle);
var localPos = rot * Vector3.right * tar.radius;
tar.spawnedObjects.Add(Instantiate(tar.clonedObject,
tar.transform.position + localPos, rot));
angle += angleBetween;
}
}
// Solutions #2 & 3
if (!Mathf.Approximately(tar.spin, tar.spinLast) ||
!Mathf.Approximately(tar.radius, tar.radiusLast))
{
tar.spinLast = tar.spin;
tar.radiusLast = tar.radius;
angle = 0f;
for (int i = 0; i < tar.numOfItems; i++)
{
var rot = Quaternion.Euler(0f, 0f, tar.spin + angle);
var localPos = rot * Vector3.right * tar.radius;
tar.spawnedObjects[i].transform.position =
tar.transform.position + localPos;
tar.spawnedObjects[i].transform.rotation = rot;
angle += angleBetween;
}
}
}
}
There are few problems in your code:
1.Notice how there is one extra prefab created. Basically the number of items created is numOfItems + 1 instead of the value of numOfItems.
That's because of this line:
for (int i = 0; i <= tar.numOfItems; i++)
That should be i < tar.numOfItems not i <= tar.numOfItems;.
2.You are not clearing the List. After destroying the items, clear the list too with tar.Clear();. If you don't do this, you will have list with null items that will keep growing.
To accomplish what's in your question, you will need a way to detect when the radius and numOfItems value changes. You need something similar to the Update function. There is none if you derive your script from Editor instead of MonoBehaviour.
To get something similar to the Update function event while deriving your script from Editor, subscribe to the EditorApplication.update event in the OnEnable function and unsubscribe from it in the OnDisable function. This should handle your #1 and #2 questions
To rotate all of them at the-same time, use the transform.RotateAround function. This should handle your #3 question.
I spent time yesterday to see how easy this is and following everything I mentioned above, below is what I came up with:
Below is the code. It can be improved. Most things are repeatable so I tried as much to put them in a function and used enum to determine which things to execute depending on the slider. Things shortened the code almost twice the original size. You don't have to use this code directly but you can use it to learn what I did and make yours if you prefer:
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(CircleSpawn))]
public class CircleSpawnEditor : Editor
{
private CircleSpawn targetObj;
private static float previousRadius;
private static int previousNumOfItems;
private static float previousAngleIncrement;
private static float angleIncrement = 0;
public override void OnInspectorGUI()
{
var tar = (CircleSpawn)target;
targetObj = tar;
makeSlider("Radius", tar.radius, 0f, 10, 24, UpdateType.RADIUS);
makeSlider("Number of Items", tar.numOfItems, 0f, 100, 24, UpdateType.RESPAWN);
makeSlider("Angle", angleIncrement, 0f, 360, 24, UpdateType.ANGLE);
//set its values
tar.radius = EditorGUILayout.FloatField("Radius:", tar.radius);
tar.numOfItems = EditorGUILayout.IntField("Number of Items:", tar.numOfItems);
angleIncrement = EditorGUILayout.FloatField("Angle:", angleIncrement);
tar.clonedObject = (GameObject)EditorGUILayout.ObjectField(tar.clonedObject,
typeof(GameObject), true);
//Inspector button for creating the objects in the Editor
if (GUILayout.Button("Create"))
{
RespawnNumOfItems();
}
}
void makeSlider(string label, float value, float min, float max, int space, UpdateType updateType)
{
GUILayout.Space(2);
GUILayout.Label(label);
if (updateType == UpdateType.RADIUS)
targetObj.radius = GUILayout.HorizontalSlider(targetObj.radius, min, max);
if (updateType == UpdateType.RESPAWN)
targetObj.numOfItems = (int)GUILayout.HorizontalSlider(targetObj.numOfItems, min, max);
if (updateType == UpdateType.ANGLE)
angleIncrement = GUILayout.HorizontalSlider(angleIncrement, min, max);
GUILayout.BeginHorizontal();
var defaultAlignment3 = GUI.skin.label.alignment;
GUILayout.Label(min.ToString());
GUI.skin.label.alignment = TextAnchor.UpperRight;
GUILayout.Label(max.ToString());
GUI.skin.label.alignment = defaultAlignment3;
GUILayout.EndHorizontal();
GUILayout.Space(space);
}
void OnEnable()
{
EditorApplication.update += Update;
}
void OnDisable()
{
EditorApplication.update -= Update;
}
void Update()
{
if (targetObj != null)
{
float clampedNewRadius = Mathf.Clamp(targetObj.radius, 0f, float.MaxValue);
//Check if Radius changed
if (RadiusChanged())
{
//Debug.Log("Radius Changed: " + targetObj.radius);
previousRadius = clampedNewRadius;
targetObj.radius = clampedNewRadius;
UpdateRadius();
}
int clampedNewNumOfItems = Mathf.Clamp(targetObj.numOfItems, 0, int.MaxValue);
//Check if NumOfItems changed
if (NumOfItemsChanged())
{
//Debug.Log("NumOfItems Changed: " + previousNumOfItems);
previousNumOfItems = clampedNewNumOfItems;
targetObj.numOfItems = clampedNewNumOfItems;
RespawnNumOfItems();
}
float clampedAngle = Mathf.Clamp(angleIncrement, 0, int.MaxValue);
//Check if Angle changed
if (AngleChanged())
{
//Debug.Log("Angle Changed: " + previousAngleIncrement);
UpdateAngle();
previousAngleIncrement = clampedAngle;
angleIncrement = clampedAngle;
}
}
}
private void UpdateAngle()
{
UpdateTransform(UpdateType.ANGLE);
}
private void RespawnNumOfItems()
{
if (targetObj.spawnedObjects == null)
targetObj.spawnedObjects = new List<GameObject>();
//clean up old objects
if (targetObj.spawnedObjects != null)
{
// Debug.LogWarning("Destroyed");
foreach (var ob in targetObj.spawnedObjects)
{
DestroyImmediate(ob);
}
}
//Clear list
targetObj.spawnedObjects.Clear();
//Debug.LogWarning("Cleared List");
UpdateTransform(UpdateType.RESPAWN);
}
private void UpdateRadius()
{
UpdateTransform(UpdateType.RADIUS);
}
void UpdateTransform(UpdateType updateType)
{
float angleBetween = 360.0f / targetObj.numOfItems;
float angle = 0;
if (targetObj != null)
for (int i = 0; i < targetObj.numOfItems; i++)
{
//For each object, find a rotation and position
var rot = Quaternion.Euler(0, 0, angle);
var localPos = rot * Vector3.right * targetObj.radius;
//Debug.LogWarning("Updated");
if (updateType == UpdateType.RADIUS)
{
//Make sure that loop is within range
if (targetObj.spawnedObjects != null && i < targetObj.spawnedObjects.Count)
{
GameObject obj = targetObj.spawnedObjects[i];
if (obj != null)
{
Transform trans = obj.transform;
trans.position = targetObj.transform.position + localPos;
trans.rotation = rot;
}
}
}
else if (updateType == UpdateType.RESPAWN)
{
if (targetObj.clonedObject != null)
{
GameObject objToAdd = Instantiate(targetObj.clonedObject, Vector3.zero, Quaternion.identity);
objToAdd.transform.position = targetObj.transform.position + localPos;
objToAdd.transform.rotation = rot;
targetObj.spawnedObjects.Add(objToAdd);
}
else
{
// Debug.LogError("Please assign the clonedObject prefab in the Scene");
}
}
else if (updateType == UpdateType.ANGLE)
{
//Make sure that loop is within range
if (targetObj.spawnedObjects != null && i < targetObj.spawnedObjects.Count)
{
GameObject obj = targetObj.spawnedObjects[i];
if (obj != null)
{
Transform trans = obj.transform;
Vector3 tagetPoint = targetObj.transform.position;
//Decide if we should rotate left or rigt
if (previousAngleIncrement > angleIncrement)
trans.RotateAround(tagetPoint, Vector3.forward, angleIncrement);
else
trans.RotateAround(tagetPoint, -Vector3.forward, angleIncrement);
}
}
}
if (updateType != UpdateType.ANGLE)
angle += angleBetween;
}
//Uncomment to test auto angle rotation over frame
//testAngle();
}
void testAngle()
{
float speed = 0.005f;
angleIncrement = (float)EditorApplication.timeSinceStartup * speed;
}
private bool RadiusChanged()
{
bool changed = !Mathf.Approximately(targetObj.radius, previousRadius)
&& !(targetObj.radius < 0);
return changed;
}
private bool NumOfItemsChanged()
{
bool changed = (targetObj.numOfItems != previousNumOfItems)
&& !(targetObj.numOfItems < 0);
return changed;
}
private bool AngleChanged()
{
bool changed = !Mathf.Approximately(angleIncrement, previousAngleIncrement)
&& !(angleIncrement < 0);
return changed;
}
public enum UpdateType
{
RADIUS, RESPAWN, ANGLE
}
}
I began to create a platformer with Monogame and C# recently and got a strange bug with ground collision. I made block and player, both use bounding collisions like this:
public Rectangle Collision
{
get => new Rectangle((int)_pos.X, (int)_pos.Y, _w, _h);
}
//Only for player entity
public Rectangle GroundCollision
{
get => new Rectangle((int)_pos.X, (int)_pos.Y+_w, _w, 1);
}
It works good, though sometimes space appears space ply and ground like so:
.
Both block and player textures are white, but distance in 1 pix is visible. After some jumps (sometimes with movement) may fix it, though after it began to hover again. Here is player update code:
public void Update()
{
if (_movable)
{
Vector2 oldPos = _pos;
_pos += new Vector2(_vel, -_acc);
if(_pos.Y >= 96)
{
bool c = Collides;
}
if (Grounded)
{
_pos += new Vector2(0, _acc);
_acc = 0;
}
/*if (Collides)
{
_pos = oldPos;
}*/
}
}
//Entity Methods
protected void _Move(float dir, float delta)
{
_vel = (float) _speed * dir * delta;
}
protected void _Gravity(float delta)
{
float fallspeed = (float) Math.Round(Phys.gravity * delta * -5f);
if (_acc - fallspeed >= 0)
_acc = fallspeed;
else if (_acc >= 0)
_acc = 0;
}
protected void _Jump(float delta)
{
_acc += _jumpForce * delta * 10f;
}
So, is there any ways to fix this?
Ok, I am having a problem with a Unity scroll panel in C#. I downloaded this character picker that consists of a script attached to a scroll panel from here:
https://www.youtube.com/watch?v=YBsGhYuTavA
It works very well but problem is I cant make it scroll vertically instead of horizontal. I have checked the "vertical" boolean instead of horizontal on the actual scroll panel then in the script I have changed the places that based off x values to y values.
I commented where, here is the script:
float[] distance;
bool dragging = false;
int minButtonNum;
int currentSelectedPly = -1;
public float objectScale = 1.7f;
public int bttnDistance = 300;
void OnEnable() {
//txtGeneralCash.text = "" + PlayerPrefs.GetInt ("money", 0);
}
void Start(){
distance = new float[prefab.Length];
//instatiate the prefab
for(int i=0; i<prefab.Length;i++){
prefab[i] = Instantiate(prefab[i],center.transform.position,camInUse.transform.rotation) as GameObject;
prefab [i].transform.SetParent(panel.transform);
Vector3 pos = prefab[i].GetComponent<RectTransform>().anchoredPosition;
pos.x += (i * bttnDistance); //**CHANGED TO POS.Y
prefab [i].GetComponent<RectTransform> ().anchoredPosition = pos;
}
}
void Update(){
//calculate the relative distance
for(int i=0;i<prefab.Length;i++){
distance [i] = Mathf.Abs (center.transform.position.x - prefab [i].transform.position.x); //CHANGED THESE TO .Y
}
float minDistance = Mathf.Min (distance);
// Aplly the scale to object
for(int a=0;a<prefab.Length;a++){
if (minDistance == distance [a]) {
minButtonNum = a;
//this is when each char is selected !!!!!!!!!!!!!!!
if(minButtonNum != currentSelectedPly){
//lookAtPrice (minButtonNum);
scaleButtonCenter (minButtonNum);
currentSelectedPly = minButtonNum;
txtName.text = prefab [minButtonNum].GetComponent<CharacterProperty> ().nameObj;
bgMat.color = prefab [minButtonNum].GetComponent<CharacterProperty> ().color;
}
}
}
// if the users aren't dragging the lerp function is called on the prefab
if(!dragging){
LerpToBttn (currentSelectedPly* (-bttnDistance));
}
}
/*
* Lerp the nearest prefab to center
*/
void LerpToBttn(int position){
float newX = Mathf.Lerp (panel.anchoredPosition.x,position,Time.deltaTime*7f); //CHANGED TO .Y
Vector2 newPosition = new Vector2 (newX,panel.anchoredPosition.y);
panel.anchoredPosition = newPosition;
}
/*
* Set the scale of the prefab on center to 2, other to 1
*/
public void scaleButtonCenter (int minButtonNum){
for (int a = 0; a < prefab.Length; a++) {
if (a == minButtonNum) {
StartCoroutine (ScaleTransform(prefab [a].transform,prefab [a].transform.localScale,new Vector3 (objectScale,objectScale,objectScale)));
} else {
StartCoroutine (ScaleTransform(prefab [a].transform,prefab [a].transform.localScale,new Vector3 (1f, 1f, 1f)));
}
}
}
/*
* If the prefab is not free, show the price button
/*
* Courutine for change the scale
*/
IEnumerator ScaleTransform(Transform transformTrg,Vector3 initScale,Vector3 endScale){
float completeTime = 0.2f;//How much time will it take to scale
float currentTime = 0.0f;
bool done = false;
while (!done){
float percent = currentTime / completeTime;
if (percent >= 1.0f){
percent = 1;
done = true;
}
transformTrg.localScale = Vector3.Lerp(initScale, endScale, percent);
currentTime += Time.deltaTime;
yield return new WaitForEndOfFrame();
}
}
/*
* Called by the canvas, set dragging to true for preventing lerp when users are dragging
*/
public void StartDrag(){
dragging = true;
}
/*
* Called by the canvas, set dragging to true for preventing lerp when users are dragging
*/
public void EndDrag(){
dragging = false;
}
/*
* Called when character is selected, it change the player model
*/
public void CharacterSelected(){
bool oneEnable = false;
string nameSelected = prefab [currentSelectedPly].GetComponent<CharacterProperty> ().name;
nameSelected = nameSelected.Split('(')[0];
GameObject player = GameObject.Find ("CharactersPlayer");
if(player != null){
foreach (Transform child in player.transform) {
if (child.gameObject.name == nameSelected) {
child.gameObject.SetActive (true);
oneEnable = true;
PlayerPrefs.SetString ("SelectedPlayer", nameSelected);
} else {
child.gameObject.SetActive (false);
}
}
// if no one was selected
if (oneEnable == false) {
player.transform.GetChild (0).gameObject.SetActive (true);
}
}
}
Ive gone through it several times and I don't know why just changing the values from .x to .y does not work. I get a bunch of glitching and the characters do stack up vertically, but they move off the screen when i try to scroll.
How can I fix this?
I am attempting to create a minigolf game but am having trouble solving how to calculate the direction to hit the ball. I am thinking that it is easiest to hit the ball in the forward direction that the camera is facing, but I get unexpected results after the first hit as the ball will not calculate the correct direction after hitting the first time. How can I set the direction for hitting the ball and then apply the force I am calculating?
This is my script attached to my ball object at the moment. Sorry for the messiness.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class Golfball : MonoBehaviour {
public GameObject ball = null;
public GameObject hole = null;
public GameObject cam = null;
public Text distance;
public Text score;
public Slider powerbar;
private int strokes = 0;
private bool isMoving = false;
private bool increasing = true;
private float distanceToHole;
public float minHitPower = 40.0f;
public float maxHitPower = 270.0f;
private float hitPower = 0;
private float powerIncrement = 5.0f;
private float powerMultiplier = 10;
private float ballRollTime = 0;
private Vector3 ballDir;
// Use this for initialization
void Start() {
distance.GetComponent<Text>().text = "Distance To Hole:" + distanceToHole;
ball.GetComponent<Rigidbody> ();
score.GetComponent<Text>().text = "Strokes:" + strokes;
}
// Update is called once per frame
void Update () {
//Allow the ball to be hit if the ball is not null, not currently moving, and the left mouse button is clicked.
if (ball != null) {
if (Input.GetButton("Fire1") && !isMoving) {
calculatePower ();
}
//Hit ball using power level and set ball to moving.
if (Input.GetButtonUp("Fire1"))
{/**********************************************
//Calculate direction to hit ball
ballDir = cam.transform.forward.normalized;
hitBall(hitPower);
isMoving = true;
}**********************************************/
//Detect when the ball stops
if (isMoving) {
ballRollTime += Time.deltaTime;
if (ballRollTime > 1 && GetComponent<Rigidbody> ().velocity.magnitude <= 0.5) {
GetComponent<Rigidbody> ().velocity = Vector3.zero;
GetComponent<Rigidbody> ().angularVelocity = Vector3.zero;
isMoving = false;
}
} else {
ballRollTime = 0;
}
}
//Calculate distance to hole
distanceToHole = Mathf.Round((Vector3.Distance(ball.transform.position, hole.transform.position) * 100f) / 100f);
distance.GetComponent<Text>().text = "Distance To Hole: " + distanceToHole;
Debug.DrawLine(ball.transform.position, ballDir, Color.red, Mathf.Infinity);
}
void calculatePower(){
//Increase power if it is less than the max power.
if (increasing)
{
if (hitPower < maxHitPower)
{
hitPower += powerIncrement * powerMultiplier;
increasing = true;
}
else if (hitPower >= maxHitPower)
{
increasing = false;
}
}
//Decrease power if power level is not increasing until the power hits the minimum level.
if(!increasing) {
//Debug.Log ("Not Increasing");
if (hitPower > minHitPower) {
//Debug.Log ("HitPower: " + hitPower);
hitPower -= powerIncrement * powerMultiplier;
} else if (hitPower <= minHitPower) {
increasing = true;
}
}
//Update the slider
powerbar.value = hitPower / powerMultiplier;
}
void hitBall (float power){
//Add force to the ball
//ball.GetComponent<Rigidbody>().AddForce(new Vector3(0, 0, power));
//Camera.main.transform.forward
ball.GetComponent<Rigidbody>().AddRelativeForce(ballDir * power);
//Increase stroke count
strokes++;
updateScore(strokes);
//Reset the power and power bar level to minimum default after hitting ball
hitPower = minHitPower;
powerbar.value = hitPower / powerMultiplier;
Debug.Log("HitPower Reset: " + hitPower);
}
void updateScore(int stroke)
{
score.GetComponent<Text>().text = "Strokes:" + stroke;
}
}
You'll have to apply the force like that:
ball.GetComponent<Rigidbody>().AddForce(ballDir * power, ForceMode.Impulse);
But you'll probably have to play with your 'power' variable.
Also note that you want to use impulse because you are hitting the ball and not applying force for some period of time.
The ballDir * power multiples the ballDir vector by your float number. You probably already know how it works, but here is the rundown:
scalarValue * new Vector(x0, x1, ..., xn) == new Vector(scalarValue * x0, scalarValue * x1, ... scalarValue * xn);]
You might also want to get rid of direction's y component so that your ball doesn't fly into the air (you are playing minigolf, right?)
ballDir.y = 0;