Having a huge problem figuring out how to do a grid-based movement or tile based movement. Somewhat similar to Nitrome's Redungeon: Can't really post an image: so here's a gif. http://www.eneminds.com/redungeon/img/gifs/gif_1.gif
I figured I should create a grid system. I thought this was easy just like the old games(Pokemon etc.) Not sure how to do that though. But I also want the movement to be fast.
Unity has its own implementation of tilemaps which should allow you to build the background a.k.a the ground you walk on:
https://docs.unity3d.com/Manual/Tilemap.html
Furthermore, Unity provides more 2d content to help you build things right here:
https://github.com/Unity-Technologies/2d-extras
Collisions are outlined in numerous tutorials found on here:
https://unity3d.com/learn/tutorials/s/2d-game-creation
To make a game like the one in the animated GIF, is not easy. This is especially the case if you're new to game dev, so don't expect easy solutions.
You might want to check out the Unity Asset Store for a quick 'n dirty way of getting tile-based movement, but this as always will come with a price and will not help you learn.
https://assetstore.unity.com/
With regards to movement speed, this is always just a variable in your game which can be tuned to your preference. In the case of a tile based game it would correspond to the time it takes for the character to be visually shifted from one tile to another. Try reading up on Lerping which is a part of the Unity engine.
https://docs.unity3d.com/ScriptReference/Vector3.Lerp.html
I was able to pull it off.
I just modified the input a little from the RogueLike Tutorial from Unity's Learn Section.
So This is the code responsible for movement
void Move(string direction_string)
{
ChangeDirection(direction_string);
Vector2 start = transform.position;
Vector2 end = start + direction;
boxCollider.enabled = false;
hit = Physics2D.Linecast(start, end, blockingLayer);
boxCollider.enabled = true;
if (hit.transform == null) {
StartCoroutine(Movement(end));
animator.SetTrigger(direction_string);
return;
} else {
moveSequence.RemoveAt(0);
animator.SetTrigger(direction_string);
return;
}
}
void ChangeDirection(string direction_string)
{
switch (direction_string) {
case "up":
direction = dir_Up;
break;
case "down":
direction = dir_Down;
break;
case "left":
direction = dir_Left;
break;
case "right":
direction = dir_Right;
break;
}
}
IEnumerator Movement(Vector3 end)
{
moving = true;
float sqrRemainingDistance = (transform.position - end).sqrMagnitude;
while (sqrRemainingDistance > float.Epsilon) {
Vector3 newPosition = Vector3.MoveTowards(rb2D.position, end, moveSpeed * Time.deltaTime);
rb2D.MovePosition(newPosition);
sqrRemainingDistance = (transform.position - end).sqrMagnitude;
yield return null;
}
currentPos = end;
moveSequence.RemoveAt(0);
moving = false;
}
}
Here is what's responsible for input
void FixedUpdate ()
{
if (moveSequence.Count > 0 && !moving) {
Move(moveSequence[0]);
}
}
Then just hook it up to an Update() function that listens to button press and add a list item to moveSequence List like
moveSequence.Add("up");
Just did a code like this for someone on reddit, came here to see if its been asked before. I know I'm 2 years late but here it goes in case anyone needs it.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameController : MonoBehaviour
{
// a script created so that a player always moves one direction at a time on a grid.
// It uses a plane with even width and height to make a grid
// - if needed, turn off the mesh renderer so that you dont see the tiled grid.
// the game object we use to create a grid. Should be a plane with an even width and height.
public GameObject tile_object;
// grid x and y sizes, set them to odd numbers
public float x_grid_size, y_grid_size;
// List of all our floor tiles
Dictionary<(float x, float y), GameObject> floor_list = new Dictionary<(float x, float y), GameObject>();
// Reference to the player prefab
public GameObject player;
// reference to player character
private GameObject player_character;
// a tool used for debugging
bool debugging = true;
//check if player is moving
bool player_is_moving = false;
(float x, float y) players_current_tile;
// players speed
public float player_speed = 35f;
// used in calculating movements
(float x, float y) to_tile;
//used to calculate movement
Vector3 to_pos;
// Start is called before the first frame update
void Start()
{
// put the player into the scene
player_character = Instantiate(player, Vector3.zero, player.transform.rotation);
// Sets the Middle Tile and checks grid is odd
SetMiddleTile();
// create a floor grid to help us visualize our play field.
CreateGrid();
}
// Update is called once per frame
void Update()
{
PlayerMovement();
MovePlayer();
}
// gets inputs on A S W D keys for movement
void PlayerMovement() {
if (Input.GetKeyDown(KeyCode.W)){
PlayerInput((0, 1));
}
if (Input.GetKeyDown(KeyCode.A))
{
PlayerInput((-1, 0));
}
if (Input.GetKeyDown(KeyCode.S))
{
PlayerInput((0, -1));
}
if (Input.GetKeyDown(KeyCode.D))
{
PlayerInput((1, 0));
}
}
// simplifies player entries, and gets input directional data
void PlayerInput((float x, float y) input)
{
if (!player_is_moving)
{
(float x, float y) to_tile_check = (players_current_tile.x + input.x, players_current_tile.y + input.y);
if (floor_list[to_tile_check] != null)
{
player_is_moving = true;
to_tile = (players_current_tile.x + input.x, players_current_tile.y + input.y);
to_pos = floor_list[to_tile].transform.position;
}
}
}
// used to move the player
void MovePlayer()
{
if (player_is_moving)
{
player_character.transform.position = Vector3.MoveTowards(player_character.transform.position, to_pos, Time.deltaTime * player_speed);
if (player_character.transform.position == to_pos)
{
players_current_tile = to_tile;
player_is_moving = false;
}
}
}
// used to create the floor grid
void CreateGrid()
{
// Create an empty to hold our floor grid
GameObject grid_empty = new GameObject();
grid_empty.name = "grid_floor";
// get the size of our floor tile so we know how far to space them.
float tile_size = tile_object.GetComponent<MeshRenderer>().bounds.size.x;
// create an offset so the grid is always centered in game view
Vector3 offSet = new Vector3((x_grid_size * tile_size) / 2 - (tile_size / 2), 0, (y_grid_size * tile_size)/2 - (tile_size / 2));
// iterate to create tiles on x axis
for (int x = 0; x < x_grid_size; x++)
{
// iterate to create tiles on y axis
for (int y = 0; y < y_grid_size; y++)
{
// instantiate new tile
GameObject floor_tile = Instantiate(tile_object, (new Vector3(x * tile_size, 0, y * tile_size) - offSet), tile_object.transform.rotation);
floor_tile.name = "flr_x: " + (x + 1) + " y:" + (y + 1);
// set the parent to grid empty so the scene isnt filled with objects and all floor tiles are neatly centerd
floor_tile.transform.parent = grid_empty.transform;
// add the tile to our dictionary.
floor_list.Add(((x + 1),(y + 1)), floor_tile);
}
}
if (debugging) { print("Created Floors: " + (x_grid_size * y_grid_size) + " Floor List Size: " + floor_list.Count);}
}
// sets the middle tile the player is spawned on, also checks to make sure grid size is set correctly
void SetMiddleTile()
{
// these check to make sure grid size isnt 0 and are odd
if (x_grid_size == 0 && y_grid_size == 0) { print("Forgot to set grid size! setting default to 7x and 7y"); x_grid_size = 7; y_grid_size = 7; }
if (x_grid_size % 2 == 0) { print("x_grid_size is set to an even number(" + x_grid_size + "), changing it to odd(" + (x_grid_size + 1) + ")"); x_grid_size += 1; }
if (y_grid_size % 2 == 0) { print("y_grid_size is set to an even number(" + y_grid_size + "), changing it to odd(" + (y_grid_size + 1) + ")"); y_grid_size += 1; }
// splits the grid and half
float x = x_grid_size / 2 + 0.5f;
float y = y_grid_size / 2 + 0.5f;
// set the players current tile to middle tile
players_current_tile = (x, y);
// set the to tile to current tile to avoid null instances when first checking our dictionary
to_tile = players_current_tile;
// used for debugging
if (debugging) { print("the middle tile is: x(" + x + ")" + " y(" + y + ")"); }
}
}
Related
I'm pretty new and I can't seem to get this to work. Players have a long pole and if they poke certain objects it starts a string-like connection to the next one they poke. These objects are tagged as "PokableObjects", and to poke players will click. I'm going to have hundreds of different pokable objects, and I want the script on the pole to work for all of them.
I think I'm misunderstanding how to reference only the objects being poked. I want the points of a Bezier Curve script, which are public Transforms, to adapt and become whatever "PokableObject" the player clicks.
This is my script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BCurve : MonoBehaviour
{
//curved line renderer stuff
private LineRenderer lineRenderer;
public Transform p0;
public Transform p1;
public Transform p2;
//Object name detection stuff
bool m_Started;
public LayerMask m_LayerMask;
//Respawn Stuff
private float clickCounter;
public GameObject newPoker;
void Start()
{
lineRenderer = GetComponent<LineRenderer>();
m_Started = true;
clickCounter = 0;
}
private void OnTriggerStay(Collider other)
{
if (other.tag == "PokableObject")
{
Collider[] hitColliders = Physics.OverlapBox(gameObject.transform.position, transform.localScale / 2, Quaternion.identity, m_LayerMask);
if (Input.GetMouseButtonDown(0) && (clickCounter == 0))
{
p0 = Collider.gameObject.position;
clickCounter++;
}
else
{
p2 = Collider.gameObject.position;
//find midpoint between p0 & p2 then lower it's Y coordinate by 1
p1 = ((p0.position.x + p2.position.x) * .05f, ((p0.position.y + p2.position.y) * .05f) - 1), (p0.position.z + p2.position.z) * .05f;
//disable current object and spawn a new one so players can repeat
Instantiate(newPoker, transform.position, Quaternion.Euler(0, 0, 0));
GetComponent<BCurve>().enabled = false;
}
}
}
void Update()
{
DrawQuadraticBezierCurve(p0.position, p1.position, p2.position);
}
void DrawQuadraticBezierCurve(Vector3 p0, Vector3 p1, Vector3 p2)
{
lineRenderer.positionCount = 200;
float t = 0f;
Vector3 B = new Vector3(0, 0, 0);
for (int i = 0; i < lineRenderer.positionCount; i++)
{
B = (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2;
lineRenderer.SetPosition(i, B);
t += (1 / (float)lineRenderer.positionCount);
}
}
}
All help is much appreciated.
Thanks
First of all never compare float values using ==! Due to floating point (im)precision this might fail even though logically you think it would match.
E.g.
0.2f * 5f / 10f == 1.0f
is not necessarily true because it might actually be 1.0000001 or 0.9999999.
This said anyway it makes no sense to use a float for a counter. You rather want to use
int clickCounter;
Then you have a bunch of , there when calculating p1. Seems like this should have been wrapped in a p1 = new Vector3( .... )
However anyway the p1 you calculate is no Transform but would rather be a Vector3 position. You don't need this as a field! Simply recalculate it later given the two Transforms p0 and p2 within DrawQuadraticBezierCurve and then you can simply make it
var p1 = (p0.position + p2.position) * .05f - Vector3.up;
There is no need for that LayerMask and OverlapBox which result you are not using anywhere at all. You already have a tag to check your hit
Collider.gamObject makes no sense. What you want is access the Transform of the object you are colliding with which simply is the other.Transform
There is no need for GetComponent<BCurve>. This component already is the BCurve so you could simply use
enabled = false;
However, as soon as you get the second point you are disabling the component so Update will never be called again.
Either you want to draw the line only once with the positions in that moment so simply call DrawQuadraticBezierCurve right away and remove your entire Update method.
Or if your objects might continue moving and you want to continuously update the line then keep Update but keep this component enabled.
In both cases you should only call DrawQuadraticBezierCurve once you already have both Transforms.
So all together it could look like this
public class BCurve : MonoBehaviour
{
//curved line renderer stuff
[SerializeField] private LineRenderer lineRenderer;
public Transform start;
public Transform end;
//Respawn Stuff
public GameObject newPoker;
// I would make this adjustable via the Inspctor
[SerializeField] private int positionCount = 200;
private void Start()
{
if(!lineRenderer) lineRenderer = GetComponent<LineRenderer>();
}
private void OnTriggerStay(Collider other)
{
if(start && end)
{
return;
}
// in general rather use CompareTag instead of ==
// the second silently fails in case of typos and is slightly slower
if (other.CompareTag("PokableObject"))
{
// Then you ALWAYS want to check fr the mouse click, not only
// if the counter is 0
if(Input.GetMouseButtonDown(0))
{
// you rather want to check here
if (!start)
{
start = other.transform;
}
// I would add a check though to not enable to click on the same object twice
else if (other.transform != start)
{
end = other.transform;
// If you anyway want the line to be drawn only ONCE with the current positions
// then directly call this here and delete Update completely
//DrawQuadraticBezierCurve(start.position, end.position);
//enabled = false;
// or even
//Destroy(this);
Instantiate(newPoker, transform.position, Quaternion.Euler(0, 0, 0));
}
}
}
}
private void Update()
{
if(start && end)
{
DrawQuadraticBezierCurve(start.position, end.position);
}
}
private void DrawQuadraticBezierCurve(Vector3 p0, Vector3 p2)
{
lineRenderer.positionCount = positionCount;
var inversePositionCount = 1f / positionCount;
var t = 0f;
var p1 = (p0.position + p2.position) * .05f - Vector3.up;
var positions = new Vector3[positionCount];
for (int i = 0; i < lineRenderer.positionCount; i++)
{
var B = (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2;
positions[i] = B;
t += inversePositionCount;
}
// Note: It is WAY cheaper to call this and set all positions at once
lineRenderer.SetPositions(positions);
}
}
derHugo gave an amazing answer, and could not have figured this out otherwise! I added a few things and it's working perfectly. The first thing was to make sure a new LineRender object is being created which a new poker object would automatically reference and have the start and end points be reset:
private void Start()
{
if (!lineRenderer) lineRenderer = GetComponent<LineRenderer>();
lineRenderer = Instantiate(lineObject, transform.position, Quaternion.Euler(0, 0, 0)).GetComponent<LineRenderer>();
start = null;
end = null;
destroy = false;
}
The second was to move both the destruction of the current poker and the instantiation of a new poker to a LateUpdate, because the line wasn't being rendered:
private void LateUpdate()
{
if (destroy == true)
{
Instantiate(newPoker, transform.position, transform.rotation).transform.SetParent(GameObject.Find("MainCamera").transform);
Destroy(this.gameObject);
}
}
Again, thank you for the help derHugo!
using (unity 2019.3.7f1) 2d.
I have a player that moves around using a pullback mechanic and has a max power(like in Angry Birds).
I'm trying to draw a line(using a line renderer) that shows the exact path the player will go. I'm trying to make the line curve just like the player's path will. so far I've only managed to make a straight line in a pretty scuffed way.
The known variables are the Jump Power and the player's position, there is no friction. and I believe gravity is a constant(-9.81). Also, I would like to have a variable that allows me to control the line's length. And, if possible, the line will not go through objects and would act as if it has a collider.
// Edit
This is my current code. I changed The function so it would return the list's points because I wanted to be able to access it in Update() so it would only draw while I hold my mouse button.
My problem is that the trajectory line doesn't seem to curve, it goes in the right angle but it's straight. the line draws in the right direction and angle, but my initial issue of the line not curving remains unchanged. If you could please come back to me with an answer I would appreciate it.
enter code here
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TrajectoryShower : MonoBehaviour
{
LineRenderer lr;
public int Points;
public GameObject Player;
private float collisionCheckRadius = 0.1f;
public float TimeOfSimulation;
private void Awake()
{
lr = GetComponent<LineRenderer>();
lr.startColor = Color.white;
}
// Update is called once per frame
void Update()
{
if (Input.GetButton("Fire1"))
{
lr.positionCount = SimulateArc().Count;
for (int a = 0; a < lr.positionCount;a++)
{
lr.SetPosition(a, SimulateArc()[a]);
}
}
if (Input.GetButtonUp("Fire1"))
{
lr.positionCount = 0;
}
}
private List<Vector2> SimulateArc()
{
float simulateForDuration = TimeOfSimulation;
float simulationStep = 0.1f;//Will add a point every 0.1 secs.
int steps = (int)(simulateForDuration / simulationStep);
List<Vector2> lineRendererPoints = new List<Vector2>();
Vector2 calculatedPosition;
Vector2 directionVector = Player.GetComponent<DragAndShoot>().Direction;// The direction it should go
Vector2 launchPosition = transform.position;//Position where you launch from
float launchSpeed = 5f;//The initial power applied on the player
for (int i = 0; i < steps; ++i)
{
calculatedPosition = launchPosition + (directionVector * ( launchSpeed * i * simulationStep));
//Calculate gravity
calculatedPosition.y += Physics2D.gravity.y * (i * simulationStep);
lineRendererPoints.Add(calculatedPosition);
if (CheckForCollision(calculatedPosition))//if you hit something
{
break;//stop adding positions
}
}
return lineRendererPoints;
}
private bool CheckForCollision(Vector2 position)
{
Collider2D[] hits = Physics2D.OverlapCircleAll(position, collisionCheckRadius);
if (hits.Length > 0)
{
for (int x = 0;x < hits.Length;x++)
{
if (hits[x].tag != "Player" && hits[x].tag != "Floor")
{
return true;
}
}
}
return false;
}
}
Here's a simple way to visualize this.
To create your line you want a bunch of points.
The points represents the player's positions after being fired after X amount of time.
The position of each point is going to be : DirectionVector * (launch speed * time elapse) + (GravityDirection * time elapse^2)
You can decide in advance how far you pre calculate the points by simulating X duration and choosing the simulation step(calculate a point every X amount of time)
To detect collision each time you calculate a point you can do a small circle cast at that location. If it hits something you can stop add new points.
private float collisionCheckRadius = 0.1f;
private void SimulateArc()
{
float simulateForDuration = 5f;//simulate for 5 secs in the furture
float simulationStep = 0.1f;//Will add a point every 0.1 secs.
int steps = (int)(simulateForDuration/simulationStep);//50 in this example
List<Vector2> lineRendererPoints = new List<Vector2>();
Vector2 calculatedPosition;
Vector2 directionVector = new Vector2(0.5f,0.5f);//You plug you own direction here this is just an example
Vector2 launchPosition = Vector2.zero;//Position where you launch from
float launchSpeed = 10f;//Example speed per secs.
for(int i = 0; i < steps; ++i)
{
calculatedPosition = launchPosition + ( directionVector * (launchSpeed * i * simulationStep));
//Calculate gravity
calculatedPosition.y += Physics2D.gravity.y * ( i * simulationStep) * ( i * simulationStep);
lineRendererPoints.Add(calculatedPosition);
if(CheckForCollision(calculatedPosition))//if you hit something
{
break;//stop adding positions
}
}
//Assign all the positions to the line renderer.
}
private bool CheckForCollision(Vector2 position)
{
Collider2D[] hits = Physics2D.OverlapCircleAll(position, collisionCheckRadius);
if(hits.Length > 0)
{
//We hit something
//check if its a wall or seomthing
//if its a valid hit then return true
return true;
}
return false;
}
This is basically a sum of 2 vectors along the time.
You have your initial position (x0, y0), initial speed vector (x, y) and gravity vector (0, -9.81) being added along the time. You can build a function that gives you the position over time:
f(t) = (x0 + x*t, y0 + y*t - 9.81t²/2)
translating to Unity:
Vector2 positionInTime(float time, Vector2 initialPosition, Vector2 initialSpeed){
return initialPosition +
new Vector2(initialSpeed.x * t, initialSpeed.y * time - 4.905 * (time * time);
}
Now, choose a little delta time, say dt = 0.25.
Time | Position
0) 0.00 | f(0.00) = (x0, y0)
1) 0.25 | f(0.25) = (x1, y1)
2) 0.50 | f(0.50) = (x2, y2)
3) 0.75 | f(0.75) = (x3, y3)
4) 1.00 | f(1.00) = (x4, y4)
... | ...
Over time, you have a lot of points where the line will cross. Choose a time interval (say 3 seconds), evaluate all the points between 0 and 3 seconds (using f) and put your line renderer to cover one by one.
The line renderer have properties like width, width over time, color, etc. This is up to you.
Hi im working on a character animation / interaction with the environment. Im trying to spawn rocks of different sizes going from the ground to where the gravity direction is applied.
Im using c# for both of my scripts (character_movements_animation.cs & powerUp.cs)
My question is how to spawn objects around my character and not through it.
Im using the code below:
/* Variables Declaration */
public GameObject rock_Small;
public GameObject rock_Medium;
public GameObject rock_Large;
private float posX, posY, posZ;
private bool checkPos = false;
//Use this for initialization
void Start() {
//Empty for now
}
// Update is called once per frame
void Update() {
if (Random.Range(0, 100) < 10) {
checkPos = false;
posX = this.transform.position.x + Random.Range(-5.0f, 5.0f);
posY = this.transform.position.y;
posZ = this.transform.position.z + Random.Range(-5.0f, 5.0f);
if(posX > 3f && posY > 3f){
checkPos = true;
}
if (checkPos == true) {
Vector3 newPos = new Vector3(posX, posY, posZ);
Instantiate(rock_Small, newPos, rock_Small.transform.rotation);
}
}
}
Also see the example in the figure.
Seems like you want to spawn objects around your character, but avoid the character's own volume.
I'd suggest something like this:
var pos = this.transform.position = Random.insideUnitCircle.normlaized * _distance; //how far away from your character. 1, based on your original code
This will create a ring aorund your character. If you want to randomize things further, you can apply smaller random offsets to this value, eg:
var pos = this.transform.position = Random.insideUnitCircle.normlaized * _distance;
pos += Random.insideUnitCircle * _offset; //how wide the ring should be. 0.1 is probably sufficient.
I say that I am a beginner.
I have a question during the project.
I'm currently implementing a card-matching game.
When I start the game, The image is loaded from the external path (D: / ..).
My question is in the code below.
public void createCardList()
{
int count_x = 0;
int count_y = 0;
GameObject parent_area = GameObject.Find("GameMain");
List<int> num = createRandomIndex(createArrayIndex());
for (int i = 0; i < card_list.Count; ++i)
{
if ((i % CARD_ROWS) == 0 && i > 0)
{
count_y += 1;
count_x = 0;
}
GameObject card_prefab_load = Resources.Load("Prefabs/card") as GameObject;
GameObject card_prefab_instantiate = Instantiate(card_prefab_load, card_prefab_load.transform.position, Quaternion.identity);
float card_position_x = (CARD_WIDTH + CARD_SPACE) * (i % CARD_ROWS) - 290f;
//Debug.Log("card_position_x" + card_position_x);
float card_position_y = count_y * (CARD_HEIGHT + CARD_SPACE) - 280f;
//Debug.Log("card_position_y" + card_position_y);
card_prefab_instantiate.transform.SetParent(parent_area.transform);
card_prefab_instantiate.transform.name = "card_" + num[i];
card_prefab_instantiate.transform.localScale = new Vector3(1f, 1f, 1f);
card_prefab_instantiate.transform.localPosition = new Vector3(card_position_x, card_position_y, 1f);
StartCoroutine(firstRotateOriginalImage());
}
}
// Rotate image
private IEnumerator firstRotateOriginalImage()
{
yield return new WaitForSeconds(2f);
GameObject findCardList = GameObject.Find("GameMain");
for (int i = 0; i < findCardList.transform.childCount; ++i)
{
// I don't know what code to put in this part.
}
}
What I want to implement is below.
When the card rotation value reaches 90 degrees,How to change an externally imported image to a Sprite Image of a child GameObject?
How to rotate the child objects 360 degrees after the first task is completed?
For example, the picture below.
Arrows indicate the order in which cards are flipped.
also, Of the 12 GameObjects, Six GameObjects try to implement a common image.
I don't know much. I need your help.
There are many ways to do that...
transform.Rotate(...)
transform.RotateAround(...)
transform.localEulerAngles = transform.localEulerAngles.X|Y|Z +
amount
transform.localRotation = transform.localRotation*relativeRotation
/*= a Quaternion*/
Something else entirely...
I'm a doubtful regarding how well I understand your question...
But it seems like you want to simply flip the cards over don't you?
The approach I'd take is to have each card as the combination of the face (rotated 180 degrees in Y) and the back, both being children of an empty GameObject.
That way you could simply rotate the Card object using transform.Rotate(0, 180, 0)
To use it in a coroutine you could do
//Speed of animation (here 0.55 seconds)
float degreesPerSecond = 200f;
//The amount you want to rotate
float maxDegrees = 180f;
//Animate
for (float f = maxDegrees; f < 0;)
{
//How much to rotate
float rot = degreesPerSecond * Time.deltaTime;
//Rotate children
foreach(var child in transform)
child.Rotate(0, rot, 0);
//Record rotation
f -= rot;
//Wait next frame
yield return null;
}
//Round to desired rotation
foreach(var child in transform)
child.position = new Vector3(child.position.x, maxDegrees, child.position.z);
I'm making a pacman clone in XNA.
So far I've drawn the tile map using 2D array, added the pills using another 2D array and made a 2D array that allows movement of pacman.
In the actual game you can press right whilst moving up, and it will wait until you're able to move right and the turn.
I have a system in place that allows a turn only when the spritePosition % 32 = 16.
This means the sprite will be centred between the walls.
I need the program to remember the last key pressed or move to the right position before turning, but i cant find a way of doing it.
Here is a bit of the code that covers what I'm trying.
public void MovementCheck()
{
presentKey = Keyboard.GetState();
spritePosition = spriteVelocity + spritePosition;
pacManRec = new Rectangle((int)spritePosition.X, (int)spritePosition.Y, pacManTex.Width, pacManTex.Height);
spriteOrigin = new Vector2(pacManRec.Width / 2, pacManRec.Height / 2);
//Press Right
if (presentKey.IsKeyDown(Keys.Right) && pastKey.IsKeyUp(Keys.Right))
{
Right();
}
}
private void Right()
{
direction = "right";
//if the next block in tile map to the right is a 1, and the sprite is centred - allow a turn
if (inputMap[(int)Math.Floor(spritePosition.Y / 32), (int)Math.Floor(spritePosition.X / 32) + 1] == 1 && (spritePosition.Y % 32 == 16))
{
rotation = ((float)Math.PI / 180);
spriteVelocity.X = 0;
spriteVelocity.Y = 0;
spriteVelocity.X = movementSpeed;
}
}
Only the right key is shown, the others are similar but the directions all change and the checks to the tile map are changed accordingly. (+1 on the X here)
ive tried things like
while (spritePosition.Y % 32 != 16)
{ spritePosition = spriteVelocity + spritePosition; }
but that just makes the sprite shoot up the screen, (kinda obviously) :(
and I tried a new Method before the Right() call
bool RightCheck()
{
if ( CONDITIONS MET HERE )
return true
else
{
//dont remember if I used this line, but something similar
spritePosition = spriteVelocity + spritePosition;
RightCheck()
}
return false; //allows program to build
}
Just an causes infinite recursion.
One solution is adding a int counter = 0; which you update in your gameloop (with counter++;) every frame/time-step. Set to 0 everytime you make a valid input and save that input.
Outlined code:
public class GameClassWhereUpdateIsDone
{
private enum Directions { None, Up, Down, Left, Right };
private int counter = 0;
private Directions currentDirection; // Current movement-direction
private Directions lastInput; // Last direction from input
public void Update(...)
{
var keyboardState = Keyboard.GetState();
if(keyboardState.IsKeyPressed(Keys.Right))
{
counter = 0;
direction = Directions.Right;
}
if(currentDirection != lastInput && counter < 5) // Allow turning 5 updates ahead.
{
// Player want to turn
if(AllowedToTurn(lastInput)
{
currentDirection = lastInput;
}
}
MoveDirection(currentDirection);
counter++;
}
private bool AllowedToTurn(Directions direction)
{
if(direction == Directions.Right)
{
return RightCheck();
}
}
}
The key idea is to keep track of movement direction and last direciton that was input...
In the original Pac-Man "pre-turning" was actually used, meaning you would start moving diagonally if you turned ahead of a corner according to: http://home.comcast.net/~jpittman2/pacman/pacmandossier.html which is an interesting read.