I've been working on a kind of spaceship game in which you take a lander and take off from the ground and fly up as high as you can get until you come crashing down. The problem I've been running into is that the camera that I've tried to implement doesn't keep the lander in the center, the lander eventually gets faster then the camera and goes off screen. I don't understand why and I've tried everything to make it work. I created a test case in which I used the camera on a simple program where a sprite moved in four directions. That one worked fine, but when I implemented the camera on my main game, it doesn't work correctly. Any help would be appreciated.
This is the camera class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace WindowsGame4.Main
{
class Camera
{
public Matrix transform;
Viewport view;
Vector2 centre;
public Camera(Viewport newView)
{
view = newView;
}
public void Update(GameTime gameTime, Game1 lander, Lander _lander)
{
centre = new Vector2(_lander.Position.X + (lander.lander.Width / 2) - 455, _lander.Position.Y + (lander.lander.Height/2)-910);
transform = Matrix.CreateTranslation(new Vector3(-centre.X - _lander.Velocity.X, -centre.Y - _lander.Velocity.Y, 0));
}
}
}
This is the main update method in Game1:
// Creates the handler to allow the use of the keyboard inputs
KeyboardState keyboard = Keyboard.GetState();
// Sets up the launch position to allow for scoring and height determining
startPos = 700f;
// Mainly for debug, allows for leaving the game whenever desired
if (keyboard.IsKeyDown(Keys.Escape))
{
Exit();
}
// The logic for the thruster, checks for if the space key is down and if
// the lander has any fuel remaining. If there is no fuel the thrusters
// will not work.
if (keyboard.IsKeyDown(Keys.Space) && !empty)
{
_lander.Accelerate(_thruster.GetAcceleration(), gameTime.ElapsedGameTime);
// Turns on the thruster
_thruster.Active = true;
// Tells the program that the lander has taken off, allowing the gravity to kick in
landed = false;
// Keeps track of the amount of fuel you have
if (_lander.Fuel > 0)
{
// Subtracts 1 from the fuel for every second that the 'SPACE' key is down
_lander.Fuel--;
}
else if (_lander.Fuel <= 0)
{
empty = true;
endPos = _lander.Position.Y;
}
}
if (keyboard.IsKeyUp(Keys.Space) || empty)
{
// Turns off the thruster as long as the player is not pressing 'SPACE'
_thruster.Active = false;
}
// This is the logic structure for the landing portion of the game, it sets up a
// level area in which the player can land on, which in turn then checks the Position.X value
// to see how many points to give based on their close proximity to the landing site.
// ** Could possibly add depth or different levels based on location, like landing on a mountain
// ** Or landing in the bottom of a crater
if (_lander.Position.Y >= 800)
{
landed = true;
// If you have died or Failed a mission 3 times then the game is over
if (_lander.Lives == 1)
{
currentGameState = GameState.GameOver;
}
// As long as you have a life left the game will continue
// This logic also is only applied if the player has landed on the ground
// ** Add a rotational fixer as to make the lander stand straight up as opposed to the way its oriented
// ** Upon landing
else if (_lander.Lives > 0)
{
// Sets the landers position to the current position that its landed at, thus stopping the gravity
// Resets the velocity, thus making sure it doesnt continue to fall
_lander.Position = new Vector2(_lander.Position.X, _lander.Position.Y);
_lander.Velocity = new Vector2();
// Sets up the first landing site, this particular one is the highset value landing site is almost right
// On top of the flag that is placed to indicate where to land.
// *** Will Not Use In Final Version, Will Substitue With Logic For Height And Cash Gained ***
if ((_lander.Position.X >= 600 && _lander.Position.X <= 650) || (_lander.Position.X <= 600 && _lander.Position.X >= 550))
{
// *** Will be implemented to display a "YOU HAVE FAILED THE MISSION" screen. ***
failure = false;
// This is for debugging purposes only, will change to be automatic once all functions are complete
if (keyboard.IsKeyDown(Keys.Enter))
{
// (1) -- Resets the position of the lander back to its original position and Velocity
// (2) -- Add 150 points to the score based on the location that the lander set down at
// (3) -- Continues on to the next level
_lander.Position = new Vector2(graphics.PreferredBackBufferWidth / 2, 100); // (1)
_lander.Velocity = new Vector2(); // (1)
_lander.Score += 150; // (2)
currentGameState = GameState.Level2; // (3)
}
}
// Sets up the second landing site, this particular one is the second highest value landing site that is
// A large circle surrounding the landing site above
else if ((_lander.Position.X >= 651 && _lander.Position.X <= 750 ) || (_lander.Position.X <= 549 && _lander.Position.X >= 450))
{
// *** Will be implemented to display a "YOU HAVE FAILED THE MISSION" screen. ***
failure = false;
// This is for debugging purposes only, will change to be automatic once all functions are complete
if (keyboard.IsKeyDown(Keys.Enter))
{
// (1) -- Resets the position of the lander back to its original position and Velocity
// (2) -- Add 50 points to the score based on the location that the lander set down at
// (3) -- Continues on to the next level
_lander.Position = new Vector2(graphics.PreferredBackBufferWidth / 2, 100); // (1)
_lander.Velocity = new Vector2(); // (1)
_lander.Score += 50; // (2)
currentGameState = GameState.Level2; // (3)
}
}
// Sets up the final landing site, this particular one is the failing portion of the map that is
// A large area that encompasses the rest of the map.
else if ((_lander.Position.X >= 751 && _lander.Position.X <= 850) || (_lander.Position.X <= 449 && _lander.Position.X >= 0))
{
// You get no points so it does not only need to done once, it can be done as many times as needed
// And it will not change the data
_lander.Score = 0;
// *** Will be implemented to display a "YOU HAVE FAILED THE MISSION" screen. ***
failure = true;
// This is for debugging purposes only, will change to be automatic once all functions are complete
if (keyboard.IsKeyDown(Keys.Enter))
{
// (1) -- Resets the position of the lander back to its original position and Velocity
// (2) -- Takes away one of your lives as a result of the players failure to land correctly
_lander.Position = new Vector2(graphics.PreferredBackBufferWidth / 2, 100); // (1)
_lander.Velocity = new Vector2(); // (1)
_lander.Lives--; // (2)
}
}
// This is just a loop that gives a visual representation of refueling before the next level begins.
for (double i = _lander.Fuel; i < 500; i++)
{
_lander.Fuel += .05;
}
}
}
// A very simple call to the Gravity and Lander classes that simulates gravity as long as the
// lander has not landed
else if (!landed)
{
_lander.Accelerate(_gravity.GetAcceleration(), gameTime.ElapsedGameTime);
}
// Moves the lander according to gravity calculated by the lander class
_lander.DoMovement(gameTime.ElapsedGameTime);
// Calculates the height achieved based off of starting height and ending height
height = startPos - endPos;
// This will rotate the lander when the keys are pressed down
// They will also check to make sure if it is landed or not
// If it's landed then it will not allow the lander to rotate.
if (keyboard.IsKeyDown(Keys.Left) && landed == false)
{
// (1) -- This will also change the angle of rotation for the thruster as to all for movement in
// That specific direction.
// Example: if you rotate to the left and turn on the thrusters you will starting moving
// to the left
rotation -= 0.1f;
_lander.Rotation = rotation;
_thruster.Rotation = rotation; // (1)
}
if (keyboard.IsKeyDown(Keys.Right) && landed == false)
{
// (1) -- This will also change the angle of rotation for the thruster as to all for movement in that specific direction.
// (2) -- This will also change the angle of rotation for the lander as to all for movement in that specific direction.
// Example: if you rotate to the right and turn on the thrusters you will starting moving
// to the right
rotation += 0.1f;
_lander.Rotation = rotation; // (2)
_thruster.Rotation = rotation; // (1)
}
// Calls the camera class to allow the screen to move with the player
camera.Update(gameTime, this, _lander);
If you guys need more code I can upload anything else you guys need
Why do you need to use a translation for a 2D camera? It's a very complicated way for something that can be done a lot simpler. A 2D camera in my opinion should just be a vector corresponding to the upper-left corner of the camera's viewport. Then just subtract the camera vector from any sprite position and it will move along with the camera.
So in your case, just set the camera vector to be at the center point minus half of the screen width and height:
Camera = SpriteCenter - new Vector2(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2);
For every drawing call, subtract the Camera vector from the actual position of a sprite.
And there you go, your sprite will always be centered.
If you are doing something special that doesn't allow this to function, then I am unable to understand your actual question. I hope this helps.
I disagree with #Sarkilas, I think using a transformation will simplify your code, not needing to specify the camera each time.
You can use this code as a base for position, origin, zoom, and rotation.
return Matrix.CreateTranslation(new Vector3(-Position, 0.0f)) *
Matrix.CreateTranslation(new Vector3(-Origin, 0.0f)) *
Matrix.CreateRotationZ(Rotation) *
Matrix.CreateScale(Zoom, Zoom, 1) *
Matrix.CreateTranslation(new Vector3(Origin, 0.0f));
The issue is that the lander is not your origin, it should be your position, while the origin should be half of your viewport size like so
Position = lander.Position;
Origin = new Vector2(view.Width / 2, view.Height / 2);
You will probably have to add half your lander position back to the origin or position if it looks a bit off.
Related
Overview
I'm making an endless runner game. In this game, I have 5 lines, I want to player smoothly switch lines something like this Ref Link
In my case, I have everything the same but instead of a car, I have a player with PlayerController attached to it.
I'm changing the player line on Button click and also on IPointerDownHandler & IPointerUpHandler
Code
Full Code
[SerializeField] private List<Vector3> lines; // 5 lines in my case. Vector3 (0,0,0) and so on ...
private int flag;
Vector3 currLine;
private void ChangeLines ()
{
// Getting Inputs
if (Input.GetKey(KeyCode.LeftArrow)) { flag = -1; }
else if (Input.GetKey(KeyCode.RightArrow)) { flag = 1; }
else flag = 0;
if (flag > 0) MoveRight ();
else if (flag < 0) MoveLeft ();
}
//I used two approaches to moving but both are not working as indented
// 1 _ using DoTween
// 2 _ using Vector3.Lerp ()
private void MoveRight ()
{
// some input delay for Ipointers
if (inputDelay > 0) return;
if (currLine == lines [lines.Count - 1]) return; // can't move right anymore
transform.DoRotate (new Vector3(0, 45, 0) , 0.2f); // rotate player toward target
transform.DoMoveX (currLine.X, 0.3f) // 0.3f is coming from inspector
.SetEase (Ease.Linear) // i almost tried all Ease
.OnComplete ( ()=> DoTween.DoRotate (new Vector3(0, 0, 0) , 0.2f));
// using Lerp
LookAt (new Vector3 (currLine.x,Y,Z));
transform.position = Vector3.Lerp(transform.position, new Vector3(currLine.x, ..,..), lineChangeCurve
.Evaluate(Time.deltaTime * lineChangeSpeed));
}
private void MoveLeft ()
{
// same code as MoveRight
}
Problem
The code I wrote is prettier much working. the player is changing lines and also rotating towards the line but I'm unable to figure out what should i need to do to make this effect look like a reference.
Can you tell me how can I achieve the same smoother effect as the reference for my player?
Here is the link that I made so far
3D Assets link
Player lines distance :
new Vector3 (-8, 0,0)
new Vector3 (-4, 0,0)
new Vector3 (0, 0,0)
new Vector3 (4, 0,0)
new Vector3 (8, 0,0)
Thanks in Advance
You seem to be mixing two different animation techniques.
You do
transform.DoMoveX (currLine.X, 0.3f)
.SetEase (Ease.Linear)
...
which starts a tween animation
and then you also do Evaluate on the lerp which seems to be a bit redundant.
Your issue with the Lerp and Evaluate is
a) that you pass in
Time.deltaTime * lineChangeSpeed
which basically means
lineChangeSpeed / frame rate (e.g. 60)
=> This is a way to small value and will most probably basically mean you don't move at all (depending on your used curve)
You want to call this every frame and pass in a value increasing from 0 to 1 here (e.g. in a Coroutine)
b) you call it only exactly once. This will not result in any movement at all .. or at least only in a single step in the first frame => which probably causes a little "hiccup"
=> get rid of the lerp line all together. At best it interferes with the tween animation and is probably the cause of it looking somewhat off
To the question about keeping the button pressed.
You have nothing to prevent this in
private void ChangeLines ()
{
// Getting Inputs
if (Input.GetKey(KeyCode.LeftArrow)) { flag = -1; }
else if (Input.GetKey(KeyCode.RightArrow)) { flag = 1; }
else flag = 0;
if (flag > 0) MoveRight ();
else if (flag < 0) MoveLeft ();
}
First of all the flag seems quite redundant. And then you would
Either change to GetKeyDown so only the first frame where the button goes down is handled
Or add a flag that ignores all input while an animation is already running
Or even both (depending on your desired UX)
So e.g.
private bool alreadyAnimating;
private void ChangeLines ()
{
if(alreadyAnimating) return;
if (Input.GetKey(KeyCode.LeftArrow))
// Or as said maybe even better
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
MoveLeft();
}
else if (Input.GetKey(KeyCode.RightArrow))
// same here
else if (Input.GetKeyDown(KeyCode.RightArrow))
{
MoveRight();
}
}
and then block and reset the flag when done with moving
alreadyAnimating = true;
transform.DoRotate (new Vector3(0, 45, 0) , 0.2f);
transform.DoMoveX (currLine.X, 0.3f)
.SetEase (Ease.Linear)
.OnComplete (()=>
{
DoTween.DoRotate (new Vector3(0, 0, 0) , 0.2f)
.OnComplete(() => { alreadyAnimating = false; })
});
I used Lerp() for these kind of conditions and it worked well all the time. As I saw your character correctly turns but it look like a snap turn. I couldn't spot an obvious error in your code. Play a little more with the rotation time and you will get your desired result. Sorry this must be a comment but dont have the reps.
The idea of the animation for switching lines is pretty simple. All you need is an animation of turning the main character to the side and back. Let's say the line change animation takes 1 second. Then you need that when moving to the left lane, the main character makes a turn to the left in 0.5 seconds and the next 0.5 seconds turns straight again. While the gameobject moves into the adjacent line, the main character will make a small smooth turn and the animation will look better. This is how it is done in the example you showed in the question. In your video, instead of this animation, the main character abruptly turns to a certain angle and returns to its original position abruptly back when it reaches the required line.
I have a LineRenderer with many positions and am try to animate the line from point a -> b, b ->c, etc...
First, I save all the positions of the line, then I reset the positionCount so that it is not visible at the start. But when I draw the lineRenderer in a loop, increasing positionCount over each iteration, and when drawing the next line starts, the previous line shakes a little and the width changes momentarily.
Here is code:
public float LineDrawSpeed;
void Start()
{
lineRenderer = GetComponent<LineRenderer>();
int lineCountInLineRenderer = lineRenderer.positionCount - 1;
var startPositions = new Vector3[lineRenderer.positionCount];
lineRenderer.GetPositions(startPositions);
lineRenderer.positionCount = 1;
StartCoroutine(LineDrawCoroutine(startPositions));
}
Here is coroutine:
IEnumerator LineDrawCoroutine(Vector3[] positions)
{
for (int i = 0; i < positions.Length - 1; i++)
{
if (lineRenderer.positionCount <= i + 1)
lineRenderer.positionCount++;
lineRenderer.SetPosition(i, positions[i]);
float timePass = 0f;
while (timePass < LineDrawSpeed)
{
var factor = timePass / LineDrawSpeed;
factor = Mathf.SmoothStep(0, 1, factor);
lineRenderer.SetPosition(i + 1, Vector3.Lerp(positions[i], positions[i + 1], factor));
timePass += Mathf.Min(Time.deltaTime, LineDrawSpeed - timePass);
yield return null;
}
}
}
The mechanic works well, but something is wrong with animation.
I found this width variation topic quite interesting.
As far as I checked, there two important points to take into account.
1.- LineRenderer render billboard lines.
https://docs.huihoo.com/unity/5.3/Documentation/en/Manual/class-LineRenderer.html
Billboards are 2D elements incrusted in a 3D world, whose orientation is automatically computed so that it always faces the camera. This explains width variations along with camera movement.
2.- If the camera is not moving, take into account that: As defined in the documentation, width attribute defines: "a width value and a curve value to control the width of your trail along its length.
The curve is sampled at each vertex, so its accuracy is limited by the number of vertices in your trail. The overall width of the trail is controlled by the width value."
https://docs.unity3d.com/Manual/class-LineRenderer.html
So as you are dinamically changing the vertexes of your line, the overall width of your trail might suffer changes.
So I think that your algorithim works fine, and that the width variations comes along with the line renderer component.
I'm pretty confident your issue lies in the fact that you are adding to the position count
over and over again. Not 100% sure though.
Regardless, below is working code for how to incrementally increase the length of the line. It's just an IEnumerator, but you can just call it in StartCoroutine and it should work
This will work for a straight line, but will take some adjustments for a curved line (but I think you could make it work if you wanted to).
To explain this a bit extra, instead of incrementing and increasing the positionCount, and then adding the vector, I simply set the positionCount to the desired amount of vectors and then incrementally fill each vector in with the desired position. When setting them all right off the bat, they default to 0,0,0, so just be sure this doesn't cause any issues for you.
Below is the code I use:
// Using IEnumerator to ensure that the line is drawn over a specific amount of time
private IEnumerator DrawLine()
{
float renderTime = 2f; // total time for full line render
int renderSteps = 75; // desired resolution of render (higher amt of steps creates smoother render)
float timeBetweenSteps = renderTime / renderSteps; // time between each render step
// Grab the LineRenderer component that is attached to the gameObject and defined in Inspector
LineRenderer lineRenderer = yourGameObject.GetComponent<LineRenderer>();
// Declare the endpoint
Vector3 endPoint = new Vector3(0, 5, 0);
// Take the end point and break into the amount of steps we need in order to create
// fully rendered line
// Create an additiveVector for the forLoop that will step through the renderSteps
// >> Start at zero becuase zero is the localOrigin
Vector3 stepVector = endPoint / renderSteps;
Vector3 additiveVector = Vector3.zero;
// Setting the line's position element count to the render resolution because we will
// have to step through
lineRenderer.positionCount = renderSteps;
// For Loop that steps through each position element in the Line
for (int i = 0; i != lineRenderer.positionCount; i++)
{
lineRenderer.SetPosition(i, additiveVector); // set the position element to the additiveVEctor
additiveVector += stepVector; // add one step to the additiveVector
yield return new WaitForSeconds(timeBetweenSteps); // Wait the appropriate step time before repeating the loop
}
}
Let me know if you have any questions. I hope this helps!
What i did is added one ThirdPersonConctroller then in menu i clicked on Window > Animator. Created Empty State called it Walk. The state is set to HumandoidWalk.
Then i added to the Hierarchy another ThirdPersonController and bit different position on the x. Now when running the game both ThirdPersonController walking.
Now i changed the second ThirdPersonController move speed to 2 so it's walking faster then the first one.
Now i wonder how to do two main things:
If i wanted to make 10 ThirdPersonController in the Hierarchy i could drag over and over again new ThirdPersonController from the Assets > Characters > ThirdPersoncharacter > Prefabs but that's a lot of work. I wonder if there is way to do it in c# script or other way to make array of ThirdPersonController ?
I want to make one waypoint so when the ThirdPersonController will get there he will turn around 180degrees and will walk back to it's origian position. And if one of the ThirdPersonController is walking faster he will turn back faster. But the idea is that both ThirdPersonController will get to the same position of theire x and will turn back.
For exmaple the first ThirdPersonController is on x = 0 y = 0 z = 0 so when he walk and get to z = 100 only z change then turn around back to z = 0
The other ThirdPersonController is on x = -1.539 y = 0 z = 0 so this time also only z change to 100 when he get to 100 to turn around back to z = 0
I have this working waypoints script in c#
The problem is that now bothp layers walking one behind the other to the waypoints and i wanted to do as before that the second player will walk beside the first one and a bit faster then the first one.
Before i did changes in the move speed and position of the second ThirdPersonController(1) in the Inspector changed the move speed to 2 and the changed the position to -1.537 but after adding the waypoints script they bot walking on same speed and one behind the other.
using UnityEngine;
using System.Collections;
public class Waypoints : MonoBehaviour {
public Transform[] waypoint;
public float patrolSpeed;
public bool loop = true;
public int dampingLook = 4;
public float pauseDuration;
private float curTime;
private int currentWaypoint = 0;
public CharacterController character;
// Use this for initialization
void Start () {
}
void LateUpdate(){
if(currentWaypoint < waypoint.Length){
patrol();
}else{
if(loop){
currentWaypoint=0;
}
}
}
void patrol(){
Vector3 nextWayPoint = waypoint[currentWaypoint].position;
// Keep waypoint at character's height
nextWayPoint.y = transform.position.y;
// Get the direction we need to move to
// reach the next waypoint
Vector3 moveDirection = nextWayPoint - transform.position;
if(moveDirection.magnitude < 1.5){
Debug.Log("enemy is close to nextwaypoint");
// This section of code is called only whenever the enemy
// is very close to the new waypoint
// so it is called once after 4-5 seconds.
if (curTime == 0)
// Pause over the Waypoint
curTime = Time.time;
if ((Time.time - curTime) >= pauseDuration){
Debug.Log("increasing waypoint");
currentWaypoint++;
curTime = 0;
}
}
else
{
Debug.Log("reaching in rotation " + moveDirection.magnitude);
// This code gets called every time update is called
// while the enemy if moving from point 1 to point 2.
// so it gets called 100's of times in a few seconds
// Now we need to do two things
// 1) Start rotating in the desired direction
// 2) Start moving in the desired direction
// 1) Let' calculate rotation need to look at waypoint
// by simply comparing the desired waypoint & current transform
var rotation = Quaternion.LookRotation(nextWayPoint - transform.position);
// A slerp function allow us to slowly start rotating
// towards our next waypoint
transform.rotation = Quaternion.Slerp(transform.rotation, rotation,
Time.deltaTime * dampingLook);
// 2) Now also let's start moving towards our waypoint
character.Move(moveDirection.normalized * patrolSpeed * Time.deltaTime);
}
}
}
Ok so here is my question,
I am trying to figure out how I would be able to register that a user has used two fingers to make a V on the screen(finger start point would be where the user touches with there two fingers and then by spreading there two fingers while moving up to make a V shape) using Unity 3d for android.
I have never done gestures involving shapes before so any advice, links or examples on how I could do this would be greatly appreciated
Thanks in advance
Graeme
Edited:
so i have been trying to figure this out while waiting for someone to help me out.
this is what I have got so far, it isn't working the way i want it to yet but I am not sure what I'm doing wrong as i have never attempted to do anything like this with gestures before. any help at all would be greatly appreciated
thanks Again
Graeme
using UnityEngine;
using System.Collections;
public class Pinchv : MonoBehaviour {
public Vector2 leftFingerStartPosition;
public Vector2 leftFingerEndPosition;
public Vector2 rightFingerStartPosition;
public Vector2 rightFingerEndPosition;
void Update () {
foreach(Touch touch in Input.touches)
{
if(touch.phase == TouchPhase.Began){
Touch leftFinger = Input.GetTouch (0);
Touch rightFinger = Input.GetTouch (1);
leftFingerStartPosition = Input.GetTouch (0).position;
leftFingerEndPosition = Input.GetTouch(0).position;
rightFingerStartPosition = Input.GetTouch(1).position;
rightFingerEndPosition = Input.GetTouch(1).position;
if(Input.touchCount == 2 && Mathf.Abs(leftFingerEndPosition.x + Screen.width - leftFingerStartPosition.x) > 20 &&
Mathf.Abs(leftFingerEndPosition.y + Screen.height - leftFingerStartPosition.y) > 60){
if(Input.touchCount == 2 && Mathf.Abs(rightFingerEndPosition.x + Screen.width - rightFingerStartPosition.x) > 20 &&
Mathf.Abs(rightFingerEndPosition.y + Screen.height - rightFingerStartPosition.y) > 60){
Debug.Log ("its a v ");
}
}
if(touch.phase == TouchPhase.Ended){
leftFingerStartPosition = Vector2.zero;
leftFingerEndPosition = Vector2.zero;
rightFingerStartPosition = Vector2.zero;
rightFingerEndPosition = Vector2.zero;
}
}
}
}
}
EDIT:
so i took your advice and tried something different but unfortunately it does not work at all.
I'm about to start pulling my hair out soon if i cant figure this out LOL. here is the new code i tried to create that do not work Can Someone Please help me solve this its been driving me nuts now for 3 days lol.
#Venkat at Axiom Studios could you help me out again it would be greatly appreciated :)
Patiently waiting
Graeme
using UnityEngine;
using System.Collections;
public class Pinchv : MonoBehaviour {
public Vector2 fingerOneStartPosition;
public Vector2 fingerOneEndPosition;
public Vector2 fingerTwoStartPosition;
public Vector2 fingerTwoEndPosition;
void Update () {
foreach(Touch touch in Input.touches)
{
if(touch.phase == TouchPhase.Began){
// Touch leftFinger = Input.GetTouch (0);
// Touch rightFinger = Input.GetTouch (1);
fingerOneStartPosition = Input.GetTouch (0).position;
fingerOneEndPosition = Input.GetTouch(0).position;
fingerTwoStartPosition = Input.GetTouch(1).position;
fingerTwoEndPosition = Input.GetTouch(1).position;
if(Input.touchCount == 2 && Mathf.Abs(fingerOneStartPosition.x - fingerOneEndPosition.x) > 700 &&
Mathf.Abs(fingerOneStartPosition.y - fingerOneEndPosition.y) > 120){
if(Input.touchCount == 2 && Mathf.Abs(fingerTwoStartPosition.x - fingerTwoEndPosition.x) > 700 &&
Mathf.Abs(fingerTwoStartPosition.y - fingerTwoEndPosition.y) > 120){
Debug.Log ("its a v ");
}
}
}
if(touch.phase == TouchPhase.Ended){
fingerOneStartPosition = fingerOneEndPosition;
fingerOneEndPosition = Vector2.zero;
fingerTwoStartPosition = fingerTwoEndPosition;
fingerTwoEndPosition = Vector2.zero;
}
}
}
public void OnGUI(){
GUILayout.Label("Where am i fingerone X : " + fingerOneStartPosition + "end position" + fingerOneEndPosition);
GUILayout.Label("Where am i fingerone X : " + fingerTwoStartPosition + "end position" + fingerTwoEndPosition);
}
}
#Graeme
Ok, I came up with some partly hacky code. It's by no means a fantastic piece of code, but it should give you a rather clear idea of how to approach the problem.
The code does work (tested with a Moto G 1st Gen, Unity 4.6), but it'll still give some incorrect results.
I've commented the portions of the class where I see potential problems, so you can use this as a decent starting point.
Try it out and let me know. There are definitely more elegant solution than this one, in fact I was working on a gesture recog library for Unity a few months ago. I should probably dust it off and finish it :P
Unity C# code
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class DetectVGesture : MonoBehaviour {
//Note : This was tested on a 1st Gen Moto G (1280 x 720 resolution, IIRC)
// and an orthographic size of 5.
//Why this matters :
//The Vector2 minimumDeltas uses Screen units, so resolution matters here
//The float maxDistBetInitPos uses World units, so if the camera's ortho size is larger, this value becomes larger as well
//Therefore, some trial and error in these values will be needed to get it to work right
//TODO : Write some code to take into account screen resolution and camera size / FOV.
//If anyone feels like editing that portion in, please feel free
//The touches used are maintained in these two lists
private List<Touch> firstTouches = new List<Touch>();
private List<Touch> secondTouches = new List<Touch>();
//This is the minimum distance in SCREEN units
//of touch.deltaTouch for a new touch in TouchPhase.Moved to register
public Vector2 minimumDeltas = new Vector2(1, 1);
//This is the maximum distance between the two initial touches
//in WORLD units for the algorithm to work
public float maxDistBetInitPos = 3f;
//These are the minimum and maximum angles between the two
//"ARMS" of the "V" for a V gesture to be recognized
public Vector2 vAnglesMinMax = new Vector2(15, 60);
// Use this for initialization
void Start () {
}
void OnGUI () {
GUI.Label(new Rect(10, 10, 100, 50), "Touches "+Input.touchCount.ToString());
if(Input.touchCount > 0)
GUI.Label(new Rect(110, 10, 100, 50), "Touch1 "+Input.touches[0].position.ToString());
if(Input.touchCount > 1)
GUI.Label(new Rect(210, 10, 100, 50), "Touch2 "+Input.touches[1].position.ToString());
}
// Update is called once per frame
void Update () {
//For this sample, we're only interested in a "V" created with
//2 fingers, so we'l ignore the rest
if(Input.touchCount == 2) {
foreach(Touch touch in Input.touches) {
//The below two lines are to allow for an early
//exit if EITHER of the fingers is stationary.
//Uncomment the lines if you want touches to be registered
//only when BOTH fingers move.
//if(touch.phase == TouchPhase.Stationary)
//return;
//This is the first time TWO fingers are registered,
//so we can use this as our starting point, where the
//touches are closest to each other.
//From here on, I'll refer this to as the BOTTOM of the "V"
if(touch.phase == TouchPhase.Began) {
CheckTouchAndAdd(Input.touches[0], Input.touches[1]);
}
//There was some movement, so let's check what it is
if(touch.phase == TouchPhase.Moved) {
//The movement in this touch is at least as much as we want
//So, we add both the touches, and we move to the next iteration
//Here, I want both the X & Y delta positions to meet my minimum
//delta distance. You can change this to either X or Y.
if(Mathf.Abs(touch.deltaPosition.x) >= minimumDeltas.x &&
Mathf.Abs(touch.deltaPosition.y) >= minimumDeltas.y) {
CheckTouchAndAdd(Input.touches[0], Input.touches[1]);
}
else {
Debug.Log("There was too less of delta!");
}
}
//The touch / touches have ended.
//So let's clear the lists for the next trial
if(touch.phase == TouchPhase.Ended) {
firstTouches.Clear();
secondTouches.Clear();
}
}//Iterate over touches in Input.touches ends
}//Input.touchCount == 2 ends
}
private void CheckTouchAndAdd (Touch touch1, Touch touch2) {
if(!firstTouches.Contains(touch1) && !secondTouches.Contains(touch2)) {
firstTouches.Add(touch1);
secondTouches.Add(touch2);
CheckForV();
}
}
private void CheckForV () {
if(firstTouches.Count < 5 || secondTouches.Count < 5) {
Debug.Log("Not enough touches to perform the check! ");
return;
}
//First, let's check if the two initial touch points
//were relatively close enough to warrant a "V"
//If they're not, we'll have an early exit
Vector3 firstTouchInitPos = Camera.main.ScreenToWorldPoint(firstTouches[0].position);
Vector3 secondTouchInitPos = Camera.main.ScreenToWorldPoint(secondTouches[0].position);
//First we check if the X distance falls within our limit of maximum distance
if(Mathf.Abs(secondTouchInitPos.x - firstTouchInitPos.x) > maxDistBetInitPos) {
Debug.Log (string.Format("The X values were too far apart! Exiting check First {0}," +
"Second {1}, Distance {2}",
new object[] { firstTouchInitPos.x, secondTouchInitPos.x,
Mathf.Abs(secondTouchInitPos.x - firstTouchInitPos.x)} ));
return;
}
//Then we check the same for Y
if(Mathf.Abs(secondTouchInitPos.y - firstTouchInitPos.y) > maxDistBetInitPos) {
Debug.Log (string.Format("The Y values were too far apart! Exiting check First {0}," +
"Second {1}, Distance {2}",
new object[] { firstTouchInitPos.y, secondTouchInitPos.y,
Mathf.Abs(secondTouchInitPos.y - firstTouchInitPos.y)} ));
return;
}
//If we reach this point, both the X & the Y positions are within the maximum distance
//we want. So, they're close enough that we can calculate the average between the two Vectors
//and assume that both these Vectors intersect at the average point. (i.e. the average point
//is the corner at the BOTTOM of the "V")
//Note that there are more elegant ways of doing this. You can always use trignometry to do so
//but for the sake of this example, this should yield fairly good results.
Vector3 bottomCornerPoint = new Vector3( (firstTouchInitPos.x + secondTouchInitPos.x) * 0.5f,
(firstTouchInitPos.y + secondTouchInitPos.y) * 0.5f );
//Now that we have our bottom point, we then calculate the Vector between this common
//bottom point, and the last touch point added to each list. From this point
//I'll refer to these two Vectors as the ARMS of the "V"
Vector3 arm1 = new Vector3( firstTouches[firstTouches.Count - 1].position.x - bottomCornerPoint.x,
firstTouches[firstTouches.Count - 1].position.y - bottomCornerPoint.y );
Vector3 arm2 = new Vector3( secondTouches[secondTouches.Count - 1].position.x - bottomCornerPoint.x,
secondTouches[secondTouches.Count - 1].position.y - bottomCornerPoint.y );
//Now let's calculate the angle between the ARMS of the "V".
//If the angle is too small (< 15 degrees), or too large (> 60 degrees),
//it's not really a "V", so we'll exit
//Note: Vector2.Angle / Vector3.Angle perform a DOT product of the two vectors
//Therefore in certain cases, you're going to get incorrect results.
//TODO : If anyone can, please change the below to use a cross product
//to calculate the angle between the Vectors.
if(Vector3.Angle(arm1, arm2) < vAnglesMinMax.x ||
Vector3.Angle(arm1, arm2) > vAnglesMinMax.y) {
Debug.Log (string.Format("The angle was outside the allowed range! Angle {0}",
new object[] { Vector3.Angle(arm1, arm2) } ));
return;
}
//If we reach this point, everything's great, we have a "V"!
Debug.Log ("There's a V gesture here!");
}
}
The logic behind the code
I've assumed the following
The "V" is always drawn with the two fingers starting close together, and move away from each other.
When the fingers move away from each other, they move in the same direction in one axis, and in opposite directions in the other axis. (For example, if the "V" is drawn exactly like the letter, then both fingers move in the positive Y axis, and one finger moves towards the negative X axis, and the other towards the positive X axis)
The fingers are relatively close together, so the average value of the two points can be used as the point where the two arms of the "V" meet. (Please read the code for an alternative mechanism)
Lousy attempt at graphically representing my thoughts below
This is a follow up question to a question that I posted last night. I have to write a game in a Windows Form for school, and I am creating a maze game in which a player must navigate through a maze before they are killed. As a maze game, some collision detection must be used to make sure that the player doesn't simply run through the walls (this would be quite a boring game). I've implemented a feature which prevents this based on the question that I asked last night, but I'm getting some weird results.
When the player touches a wall, the game stops them, and the player ends up getting stuck. The player CANNOT move unless they press a combination of keys to move through the wall (my game uses WASD, so if I touch a wall, I can press W + A and go through the wall to the other side where my player gets unstuck).
This is my collision code:
// This goes in the main class
foreach (Rectangle wall in mazeWalls)
{
if (playerRectangle.IntersectsWith(wall))
{
player.Stop();
}
}
This is the player's movement code:
public void Move(Direction dir)
{
// First, check & save the current position.
this.lastX = this.x;
this.lastY = this.y;
if (dir == Direction.NORTH)
{
if (!CheckCollision())
{
this.y -= moveSpeed;
}
else
{
this.y += 1;
}
}
else if (dir == Direction.SOUTH)
{
if (!CheckCollision())
{
this.y += moveSpeed;
}
else
{
this.y -= 1;
}
}
else if (dir == Direction.EAST)
{
if (!CheckCollision())
{
this.x += moveSpeed;
}
else
{
this.x -= 1;
}
}
else if (dir == Direction.WEST)
{
if (!CheckCollision())
{
this.x -= moveSpeed;
}
else
{
this.x += 1;
}
}
}
My CheckCollision() method:
private bool CheckCollision()
{
// First, check to see if the player is hitting any of the boundaries of the game.
if (this.x <= 0)
{
isColliding = true;
}
else if (this.x >= 748)
{
isColliding = true;
}
else if (this.y <= 0)
{
isColliding = true;
}
else if (this.y >= 405)
{
isColliding = true;
}
else if (isColliding)
{
isColliding = false;
}
// Second, check for wall collision.
return isColliding;
}
The Stop() method:
public void Stop()
{
this.x = lastX;
this.y = lastY;
}
Here is a gif that I have uploaded so that you can see the behavior of the player with the maze walls. Notice how he slides through the walls and repeatedly gets stuck.
My question is how do I get this player to stop sticking and actually be able to slide and move with the walls? I've tried multiple collision patterns, and used last night's (very helpful) answer, but he won't stop sticking to the walls! Let me know if you need any other details/information.
EDIT: The Input code, as requested by Dan-o: http://pastebin.com/bFpPrq7g
Game design is a complicated subject. There are some pretty well documented strategies for implementing 2D maze games. The most anyone can do here is to make general suggestions based on what you've done knowing that a commercial game is not what trying to make here. So anyhow, I'll throw in my two cents. I've only done anything like this in OpenGL, not with Windows Forms.
The first thing I see is that your sprite doesn't stay centered in it's lanes. Calculating that will ultimately make things easier because there are always only 4 possible directions that the player can be moving. North, South, East, and West don't mean as much when diagonal is also possible.
Design your gameboard so that your walls and your lanes are the same width. Use evenly spaces rows and columns. Since you're using a fixed board size, this should be as simple as dividing your width and height by the number of rows and columns you want.
Once you've done this, you will always know what lane you're in based on your x and y coordinates. If your columns are 80 pixels wide, for instance, and you're at x = 160 then you know you're in column 2 (1 in a 0-based array). This is the only way you are going to be able to put enemies on the board and be able to programmatically track where they are going. Also, you'll be able to size your players and enemies appropriately for the lanes.
Let's say your rows and columns are 80Wx60H pixels (they can be whatever you like best). Now let's say that your player is moving East starting from 0, 160. And, he can move North and South when he gets to column 3 (2 in a zero-based model), according to your map. Meaning, when he gets to 160, 160. Remember, if x = 0 starts column 1, then 80 starts column 2, and so on.
Forgetting collisions for a moment, you could use logic like this.
RectangleF playerRectangle = new RectangleF();
int COLUMN_WIDTH = 80;
int ROW_HEIGHT = 60;
if (playerRectangle.IntersectsWith(wall)){
int column = playerRectangle.X / COLUMN_WIDTH;
//----------------------------------------------
// This will return false if the player
// is not positioned right at the column. The
// result of % will contain decimal digits.
// playerRectangle.X has to be a float though.
//----------------------------------------------
if(column % 1 == 0){
//--------------------------------------------
// do this based on your keyboard logic. this
// is pseudo-code
//--------------------------------------------
if(keys == keys.UP){
// Move up
}
else if(keys == keys.DOWN){
// Move down
}
}
}
Another thing you'll want to do is to store an orientation property with your walls. That way when you collide with one, you'll know what your options are for movement. If the wall you collide with is WallOrientation.NorthSouth, you can only move North or South once you hit it. Or, you can go in reverse to the direction you hit it from. If no keys are pressed you sit still. Some thing like this will do.
public enum WallOrientation{
NorthSouth,
EastWest
}
None of this code is tested. I just wrote it on the fly so there could be mistakes. I'm just trying to give you some ideas. I hope this advice helps you out some.