C# Enemy doesn't follow player in Windows Form (not XNA) - c#

UPDATE
I managed to figure the answer out myself, what was happening was that my movement function was only registering the players original static position instead of the dynamic coordinates as the player moved around the screen. What I did was create a 'God' class to publicly store the players current X and Y coordinates, and then reference the 'god' class coordinates in my enemy's movement function.
Now, not only does the enemy detect when the player is within it's sights, it will follow the player changing directions accordingly, until it can't 'see' them anymore :D
If anyone wants to see the updated code just ask :)
I'm trying to make a program that contains player and enemy sprites, but I'm having a little trouble with the code for the 'Enemy'. Most of the coding problems on here deal with XNA, which I'm not using, so I was wondering if I could have a little help.
Basically, when the player moves within a certain distance, the movement function for the enemy should activate and it will move towards the players location as long as they're in the 'detection zone'. The enemy's sprite also has a walk animation for each direction, and it should flip through frames depending on whether it's currently moving or not.
The movement function is within a timer, so that it'll update with every tick. I have two separate functions for updating its location, one to physically change it's coordinates in the Form based on the data received from the movement function, and another to update the current frame from my sprite sheet.
The problem is that the enemy doesn't move whatsoever, if I get rid of it's detection and the code to follow the player, and instead just move it across the screen, it works perfectly fine. But unfortunately that's not what I'm trying to achieve.
The code works perfectly fine for my player sprite (albeit, it's controlled by the user and the keyboard keys), so I'm not sure what I'm doing wrong.
I believe the problems lies within the Movement function, but I'm not sure what that problem is exactly.
Here's my code for the relevant functions:
public void Movement() //This function is in a timer, and determines which direction the sprite will move and face
{
//The sprite rectangle is the position of the player sprite, and detection is the area where the enemy would 'see' the player if they entered it
sprite = new Rectangle(charX - 1, charY + 1, charWidth * 2 + 2, charHeight * 2 + 2);
Detection = new Rectangle(curX - 25, curY - 25, Enemy.Width * 6, Enemy.Height * 6);
if (Detection.IntersectsWith(sprite)) //Checks to see if the player is within range
{
//Moves the enemy based on the players position and changes direction accordingly
if (curX > charX)
{
curDirection = RIGHT;
dX = -1;
dY = 0;
numUpdates = 0;
curAnimation = MOVING;
}
else if (curX < charX)
{
curDirection = LEFT;
dX = 1;
dY = 0;
numUpdates = 0;
curAnimation = MOVING;
}
if (curY > charY)
{
curDirection = UP;
dY = -1;
dX = 0;
numUpdates = 0;
curAnimation = MOVING;
}
else if (curY < charY)
{
curDirection = DOWN;
dY = 1;
dX = 0;
numUpdates = 0;
curAnimation = MOVING;
}
}
//If not within range, don't move the sprite
else
{
dY = 0;
dX = 0;
curAnimation = FINISHED;
}
}
These are the updating functions that move the enemy and change the current frame
public void Follow() //Physcially moves the enemy across the form
{
int xMod = 0;
int yMod = 0;
if (dX != 0)
xMod = dX / Math.Abs(dX) * 2;
if (dY != 0)
yMod = dY / Math.Abs(dY) * 2;
//Moves the sprite across the x and y axis
curX += xMod;
dX += -xMod;
curY += yMod;
dY += -yMod;
}
public void UpdateFrame(int direction = DOWN)
{
numUpdates++;
switch (curAnimation)
{
//Stops the animation if the sprite stops moving
case FINISHED:
curFrame = 0;
break;
//Moves to the next frame when movement is found
case MOVING:
curFrame = (curFrame + 1) % 3;
Follow();
//check if done animation
if (dX == 0 && dY == 0) curAnimation = FINISHED;
break;
}
}
If you need more code just ask, any help is appreciated :)

Related

Unity: How to rotate child weapon around pivot point in its local space

In my 2D game, the player Prefab has children which are weapons (like swords) so that when the player moves, the weapons translate in world space with his movements while maintaining a constant local position (until of course it is time to attack). The weapons automatically point towards nearby enemies, and swing when they get close enough.
I want the weapon to follow a swing arc by rotating around a pivot point defined at half of the weapon's range in the direction of the enemy. Once the weapon starts the swing, the arc's local position and rotation should remain unchanged and no longer care about the enemy position while the world arc will obviously translate with the player. The weapon should follow this arc purely relative to the player.
RotateAround seems to only work in world space, and thus causes strange problems when trying to rotate around an object in world space while the weapon's world position (as well as my desired pivot point's world position) would be translating with the player. Also, the point that I need it to rotate around needs to be relative to the local space, since when the player moves, the weapon needs to maintain its local arc while also translating with the player.
I also tried using Vector3.Slerp on the weapon's transform.localPosition, which seemed like it would be the perfect solution, but I can't seem to get the arc to match what I envision a good round swing would look like.
The attack consists of three parts: Backswing, Foreswing, and Recovery. The part that I care most about is the foreswing arc, as the others can be acheived easily with simply local rotations and lerping.
const int BACKSWING = 0;
const int FORESWING = 1;
const int RECOVER = 2;
float[] timeFrac = { .15f, .25f, .6f };
float[] rotations = { 120f, -240f, 120f };
float backSwingDistMultiplier = .5f;
//Swing Attack
public override IEnumerator Attack(float startAngle) {
var totalAttackTime = GetAttackTime();
var backSwingDist = backSwingDistMultiplier * Range;
var startPos = transform.localPosition;
var slerpCenterDiff = PerpDir(dir).normalized;
//Interpolation arrays
float[] swingTimes = { timeFrac[BACKSWING] * totalAttackTime,
timeFrac[FORESWING] * totalAttackTime,
timeFrac[RECOVER] * totalAttackTime };
float[] startAngles = { startAngle,
startAngle + rotations[BACKSWING],
startAngle + rotations[BACKSWING] + rotations[FORESWING] };
Vector3[] swingPositions = { startPos - (dir - slerpCenterDiff ) * backSwingDist,
startPos + dir * Range + slerpCenterDiff * backSwingDist };
Vector3[] slerpCenters = { (startPos + swingPositions[BACKSWING]) * .5f + slerpCenterDiff ,
((swingPositions[BACKSWING] + swingPositions[FORESWING]) * .5f) + slerpCenterDiff };
Vector3[] slerpStarts = { startPos - slerpCenters[BACKSWING],
swingPositions[BACKSWING] - slerpCenters[FORESWING]};
Vector3[] slerpEnds = { swingPositions[BACKSWING] - slerpCenters[BACKSWING],
swingPositions[FORESWING] - slerpCenters[FORESWING]};
timer = 0;
float percentDone;
//A swing attack has backswing, foreswing, and recovery
for (int swing = 0; swing <= 2; swing++) {
while (timer < swingTimes[swing]) {
percentDone = timer / swingTimes[swing];
//Backswing and Foreswing will slerp
if (swing < RECOVER) {
transform.localPosition = Vector3.Slerp(slerpStarts[swing], slerpEnds[swing], percentDone);
transform.localPosition += slerpCenters[swing];
} else { //Recover will lerp
transform.localPosition = Vector3.Lerp(swingPositions[FORESWING], startPos, percentDone);
}
transform.localRotation = Quaternion.Euler(0, 0,
startAngles[swing] + rotations[swing] * percentDone);
timer += Time.deltaTime;
yield return null;
}
transform.localRotation = Quaternion.Euler(0, 0, startAngles[swing] + rotations[swing]);
timer -= swingTimes[swing];
}
transform.localPosition = startPos;
}
I would just make an animation out of it, but I need the range to be dynamic, which is next to impossible to achieve with keyframes.
I was able to get my desired outcome by defining the arc centers in localPosition first:
var backswingArcCenter = (startPos + swingPositions[BACKSWING]) * 0.5f;
var foreswingArcCenter = (swingPositions[BACKSWING] + swingPositions[FORESWING]) * 0.5f;
and then calling RotateAround using that position added to the player's world space
if (swing == FORESWING) {
transform.RotateAround(player.transform.position + foreswingArcCenter,
Vector3.forward, Time.deltaTime / swingTimes[swing] * 270f);
} else if (swing == BACKSWING) {
transform.RotateAround(player.transform.position + backswingArcCenter,
Vector3.forward, Time.deltaTime / swingTimes[swing] * -180f);
}

Tilemap.SetTile only works with Vector3Int, but my tile size is in 0.16f increments

So in my Unity2D game, I need to change tiles inside a tilemap from within a script. I quickly found the Tilemap.SetTile function which asks for a Vector3Int position in his parameters, however my tile size is in 0.16f increments, which means that my tiles' Vector3 positions look like (0, -0.16f, 0).
public class BreakableWalls : MonoBehaviour
{
private float tileSize = 0.16f;
public int hp;
public Tilemap tilemap;
public TileBase fullTile;
public TileBase brokenTile;
public void Start()
{
for (float i = 0; i > -(tileSize * 3); i -= tileSize)
{
Vector3 currentTilePos = new Vector3(0, 0 - i, 0);
TileBase tile = hp == 2 ? fullTile : brokenTile;
tilemap.SetTile(currentTilePos, tile);
}
}
}
The principle is simple. I have breakable walls that can either start with 2 HP (need to be shot twice to be fully broken) or 1 HP (need to be shot once to be fully broken). A wall is composed of 3 tiles and I'm going through each tile (starting from the top one, as the top tile's position is always Vector3.zero) to update their appearance based on how much HP the wall has.
If my tileSize would be 1, this would work fine since SetTile needs a Vector3Int, but as of right now it won't work because of my 0.16f tileSize.
How can I work around this?
Thanks,
tileSize doesn't affect indexing by cell. Simply do this:
for (int i = 0; i > -3 ; i -= 1)
{
Vector3Int currentTileCellPos = new Vector3Int(0, i, 0);
TileBase tile = hp == 2 ? fullTile : brokenTile;
tilemap.SetTile(currentTileCellPos, tile);
}
You don't actually seem to need the world position of the tile - you never use it in the question. And if you do, you can always do Vector3 currentTileWorldPos tilemap.CellToWorld(currentTileCellPos);

Detecting controller movement in same direction as VR button

I'm working on a VR project and I'm struggling to do something that should not be that hard, but I really am bad at math :x
What I want to do, is have "physical" buttons on the world that I can push with my finger to press/unpress it, this part works really well and the feeling is perfect, BUT :
Currently, to "push" the button, I'm using the z delta position of my controller, so I can't push button from the top for example, because I would need the y delta.
I'm trying to be able to have buttons facing any direction, and be able to push them using the right axis of my controller, but can't get my head around it :x
I'd need to be able to push the button depending on it's position/rotation.
I don't know if it's really understandable (probably not), maybe I just missed something really obvious.
Here is my code for my button, the enum part is probably useless, I'm just trying things lol
using System;
using UnityEngine;
public class JVRButton : MonoBehaviour, IJVRFingerInteract
{
[SerializeField] private Vector3 pressedPosition;
[SerializeField] private Vector3 defaultPosition;
[SerializeField] private Vector3 unpressPosition;
[SerializeField] private LinearDragNormal direction;
public bool Pressed { get; private set; }
public event Action<bool> OnStateChange;
private bool _changedState;
private Transform _transform;
private Vector3 _tmp;
private float _delay;
private float _delta;
private void Awake()
{
_transform = transform;
_transform.localPosition = Pressed ? pressedPosition : defaultPosition;
}
private void Update()
{
_delay += Time.deltaTime;
if (_delay < 0.1f) return;
// "Spring" effect
_transform.localPosition = Vector3.MoveTowards(_transform.localPosition, Pressed ? pressedPosition : defaultPosition, Time.deltaTime / 2);
}
public void JVRFingerInteract(JVRFinger jvrFinger)
{
Vector3 test = Quaternion.FromToRotation(jvrFinger.transform.forward, _transform.forward).eulerAngles;
Debug.Log(test);
switch(direction)
{
case LinearDragNormal.XPositive:
_delta = Mathf.Min(jvrFinger.JVRController.DeltaPositionLocal.z, 0);
break;
case LinearDragNormal.XNegative:
_delta = Mathf.Max(jvrFinger.JVRController.DeltaPositionLocal.z, 0);
break;
case LinearDragNormal.YPositive:
_delta = Mathf.Max(jvrFinger.JVRController.DeltaPositionLocal.y, 0);
break;
case LinearDragNormal.YNegative:
_delta = Mathf.Min(jvrFinger.JVRController.DeltaPositionLocal.y, 0);
break;
case LinearDragNormal.ZPositive:
_delta = Mathf.Min(jvrFinger.JVRController.DeltaPositionLocal.x, 0);
break;
case LinearDragNormal.ZNegative:
_delta = Mathf.Max(jvrFinger.JVRController.DeltaPositionLocal.x, 0);
break;
default:
throw new ArgumentOutOfRangeException();
}
MoveButton(Pressed ? unpressPosition : pressedPosition);
}
private void MoveButton(Vector3 position)
{
if (_changedState && _delay < 0.5f) return;
_delay = 0;
_changedState = false;
_tmp = _transform.localPosition;
_tmp = Vector3.MoveTowards(_tmp, position, _delta);
if (_tmp.x < position.x) _tmp.x = position.x;
if (_tmp.y < position.y) _tmp.y = position.y;
if (_tmp.z < position.z) _tmp.z = position.z;
_transform.localPosition = _tmp;
if (_transform.localPosition == pressedPosition)
{
Pressed = true;
_changedState = true;
OnStateChange?.Invoke(Pressed);
}
else if (_transform.localPosition == unpressPosition)
{
Pressed = false;
_changedState = true;
OnStateChange?.Invoke(Pressed);
}
}
}
public enum LinearDragNormal
{
XPositive,
XNegative,
YPositive,
YNegative,
ZPositive,
ZNegative
}
JVRFingerInteract is called every frame my finger is touching the button, I'm simply doing a overlapsphere in my Finger to get interactable objects.
The pushing axis of the button is the button's local Z axis, and positive Z points out of the surface of the button.
Ok, I found the solution by trying, I don't understand HOW and WHY it works, but it does...
_delta = (_forward.x * jvrPos.z + _forward.y * -jvrPos.y + _forward.z * -jvrPos.x);
if (_delta < 0) _delta = 0;
_forward being the button forward, and jvrPos being the local delta of my controller/finger
Assuming the surface of your button faces the button's local up direction:
Then you can use a dot product between the button's up in world space and the delta in world space to determine how much the finger is moving against that direction:
Vector3 worldFingerDelta;
Transform buttonTransform;
float deltaInButtonDown = Vector3.Dot(worldFingerDelta,
-buttonTransform.up);
// when deltainButtonDown is positive, the finger is moving in the
// same direction as "pushing the button"
// When it is negative, the finger is moving in the opposite direction.
// The magnitude of deltaInButtonDown is how much the finger is
// moving in that direction.
If a different local direction is pointing out of the surface of the button, such as its negative forward, you would just use the corresponding local vector in world space:
float deltaInButtonDown = Vector3.Dot(worldFingerDelta,
buttonTransform.forward); // negatives cancel out
Also, if the surface the button is on is moving in world space, you'll want to use Vector3.Dot(worldFingerDelta - worldButtonSurfaceDelta, buttonTransform.forward); in order to allow for things like the button pressing itself by moving into a still finger.
About your answer....
_delta = (_forward.x * jvrPos.z + _forward.y * -jvrPos.y
+ _forward.z * -jvrPos.x);
if (_delta < 0) _delta = 0;
_forward being the button forward, and jvrPos being the local delta of my controller/finger
As far as the vector math goes, you're doing a dot product between the button's forward and the local delta after some kind of inversion/reflection transformation is applied to it. So another way to write your answer is this:
new Vector3 vec = new Vector3(jvrPos.z, -jvrPos.y, -jvrPos.x);
_delta = Vector3.Dot(vec, buttonTransform.forward);
As we discovered in the comments of your answer, that transformation, based on the world rotation of the parent of the controller, turns your local delta into the negative of the world delta. So by taking the negative of what you have for the vector and making that negative in the Dot call, another way to write your answer is this:
new Vector3 calculatedWorldFingerDelta = new Vector3(-jvrPos.z, jvrPos.y, jvrPos.x);
_delta = Vector3.Dot(-calculatedWorldFingerDelta, buttonTransform.forward);
And since the dot product of A and -B equals the dot product of -A and B, your answer is equivalent to:
new Vector3 calculatedWorldFingerDelta = new Vector3(-jvrPos.z, jvrPos.y, jvrPos.x);
_delta = Vector3.Dot(calculatedWorldFingerDelta, -buttonTransform.forward);
Which, if calculatedWorldFingerDelta equals worldFingerDelta, is the same formula above for a button with the positive z direction pointing out of the surface of the button.
I would use worldFingerDelta directly because if the world rotation or position of the parent of the finger changes, then the transformation you're using now won't be guaranteed to give you calculatedWorldFingerDelta that equals worldFingerDelta, and your answer won't work anymore.

Can I build an AI that is more human beatable than this?

I've been building an AI and currently I'm to the point where it mostly works as it should. I hit a few hitches at first, and had something that.. spazzed out. Now that is fixed, but there's a problem - this AI catches the ball 90% of the time and returns it, although somewhat slowly at times. I have yet to build the Attract mode of the game, but I can imagine... when that is in, the two AI will almost never score on eachother.
The game takes place on a 2D plane. The AI moves purely on X, locked into Z and Y while the ball moves on X and Y.
The playing field is set up like tennis, a 'net' in the center - 0,0,0, denotes the middle and the ball moves like a tennis ball does.
The AI (and player) uses two controls - 'Direction' to go either left or right, 1 for left, -1 for right, and a bool to trigger the ball return.
At the moment I just want it to 'miss' the ball every so often, not nearly perfectly every time. I'm programming in C#, with the game engine being Unity3D - here's my code for the AI engine thus far:
void AIHandle()
{
DetectBalls();
if (GameController.Instance.Balls.Count < 1) {
GetFlipperButton = false;
}
if (BallPosition) {
BallPosit = BallPosition.position;
BallX = BallPosit.x;
//BallVelPosit = BallPosition.gameObject.rigidbody.velocity;
//BallPosit.x += BallVelPosit.x;
//BallPosit.y += BallVelPosit.y;
//BallVelPosit.y -= Physics.gravity.y;
//BallHitPredictor = new Vector3 (BallPosit.x, 0, 0);
transformPoint = transform.InverseTransformPoint(BallPosit);
//////////////////
Vector3 Heading = BallPosit - transform.position;
if (BallX > 0) {
Debug.Log(Heading.sqrMagnitude);
// THe ball is to the left //
if (transformPoint.z < 0f)
{
Direction = 1;
}
// The ball is to the right //
if (transformPoint.z > 0f)
{
Direction = -1;
}
if (Heading.sqrMagnitude < 30.0f)
{
Direction = 0;
}
} else {
Retreat();
}
} else {
Retreat();
}
MovementDirection = Mathf.Lerp(MovementDirection, Direction, 2.0f);
}
private void Retreat()
{
float DistanceToCenter = Vector3.Distance(transform.position, PlayerStartPosition);
Vector3 CenterPointOrient = transform.InverseTransformPoint(PlayerStartPosition);
if (DistanceToCenter >= 1f) {
if (CenterPointOrient.z > 0) {
Direction = -1;
} else {
Direction = 1;
}
} else {
Direction = 0;
}
}

Object vibrating on floor in XNA

I tried creating a simple physics based "game" where you can spawn balls and they then get affected by different natural force, like gravity.
But I have a problem, when the ball reaches the floor, and it keeps getting affected by gravity. It kind of vibrates. Here's a quick example of how it looks.
And here is the code for collision detection and the gravity calculation.
public void CheckBounds(int maxHeight, int maxWidth)
{
if (this.position.Y + (radius + radius) > maxHeight)
this.velocity.Y = -Math.Abs(velocity.Y * 0.65f); // 0.65 is the "bouncyness"
}
Here is the main Update section
for (int i = 0; i < balls.Count; i++ )
{
Ball b = balls[i];
for (int k = 0; k < balls.Count; k++) // check for collision with other balls
{
if (balls[i] == balls[k])
continue;
if (balls[i].Colliding(balls[k]))
balls[i].Collide(balls[k]);
}
b.velocity.Y += gravitationalPull; // a gravity constant
b.CheckBounds();
b.position = Vector2.Add(b.position, b.velocity);
}
Does anyone know how to fix the vibration? I tried setting a minimum value of velocity that the gravity would be calculated (if velocity.y < some value then dont calculate), but then the ball stopped mid air at collisions and even at spawn.

Categories

Resources