Moving Platforms in a Tile Map - c#

I am using a tile map where if a moving platform hits a tile it should go another direction. I am using a list for both the tilemap and platform, this helps keep things neat.
The image below shows the platform, it is constantly in motion and when it collides with one of the black circles it should change directions and head the opposite way it was going.
Unfortunately I am having a problem where I need to find the correct platform so it will go the other direction. I created the list in the main class and do not know how to call upon the list inside a another class besides the main.
Question:
How would I call upon a list that is holding multiple objects going in different directions, in different position, and is created in the main class, so that I can know if the platform is colliding with a tile?
Simply put, how do I make the platform collide with a tile?
Here is the process that the tile map must go through to be used and called:
To make the tile map two classes are used, Block class and Game1 class (the main class).
Inside the Block class the Texture, Position, and BlockState (which decides what thing it will do)
public Block(Texture2D Texture, Vector2 Position, int BlockState)
{
this.Texture = Texture;
this.Position = Position;
this.BlockState = BlockState;
}
Then the Block class will use a method that uses the Player class.
public Player BlockCollision(Player player)
{
Rectangle top = new Rectangle((int)Position.X + 5, (int)Position.Y - 10, Texture.Width - 10, 10);
Rectangle bottom = new Rectangle((int)Position.X + 5, (int)Position.Y + Texture.Height, Texture.Width - 10, 10);
Rectangle left = new Rectangle((int)Position.X - 10, (int)Position.Y + 5, 10, Texture.Height - 10);
Rectangle right = new Rectangle((int)Position.X + Texture.Width, (int)Position.Y + 5, 10, Texture.Height - 10);
if (BlockState == 1 || (BlockState == 2 && !player.goingUp))
{
if (top.Intersects(new Rectangle((int)player.Position.X, (int)player.Position.Y, player.Texture.Width, player.Texture.Height)))
{
if (player.Position.Y + player.Texture.Height > Position.Y && player.Position.Y + player.Texture.Height < Position.Y + Texture.Height / 2)
{
player.Position.Y = player.ground = Position.Y - player.Texture.Height;
player.Velocity.Y = 0;
player.isJumping = false;
player.Time = 0;
player.botCollision = true;
}
}
}
return player;
}
Then the Draw method.
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(Texture, new Rectangle((int)Position.X, (int)Position.Y, Texture.Width, Texture.Height), Color.White);
}
We then move over to the main class Game1.
I create the Lists for both Blocks and the Platforms.
List<Block> Blocks;
List<MovingPlatform> Platforms;
Using a char system I create the map.
List<char[,]> Levels = new List<char[,]>();
public int tileWidth, tileHeight;
Then in the Initialize method I call on Lists and create the map.
protected override void Initialize()
{
Blocks = new List<Block>();
Platforms = new List<MovingPlatform>();
char[,] Level1 = {{'.','.','#'},
{'.','.','#'},
{'.','.','#'}};
Levels.Add(Level1);
base.Initialize();
}
Then a void LoadLevel is called on.
void LoadLevel(int level)
{
Blocks.Clear();
Platforms.Clear();
player.Position = Vector2.Zero;
tileWidth = Levels[level].GetLength(1);
tileHeight = Levels[level].GetLength(0);
Texture2D blockSpriteA = Content.Load<Texture2D>("blockA2");
for (int x = 0; x < tileWidth; x++)
{
for (int y = 0; y < tileHeight; y++)
{
//Background
Blocks.Add(new Block(background, new Vector2(x * 50, y * 50), 0));
//Impassable Blocks
if (Levels[level][y, x] == '#')
{
Blocks.Add(new Block(blockSpriteA, new Vector2(x * 50, y * 50), 1));
}
//Vertical Moving Platform
if (Levels[level][y, x] == '=')
{
Platforms.Add(new MovingPlatform(platform, new Vector2(x * 50, y * 50), 3.0f, true));
}
Then In the Update I call on the lists.
foreach (Block b in Blocks)
{
player = b.BlockCollision(player);
}
foreach (MovingPlatform m in Platforms)
{
player = m.BlockCollision(player);
m.Update(gameTime);
}
Finally the Draw method calls on them.
foreach (Block b in Blocks)
{
b.Draw(spriteBatch);
}
foreach (MovingPlatform m in Platforms)
{
m.Draw(spriteBatch);
}

I think I understand your question, I am not sure, but I am going to hazard an answer anyway.
I think what you need to do is to move stuff out of the main Game class. You have a concept of a map and a bunch of stuff that belongs to a map (platforms, blocks) so it's a good idea to encapsulate this in its own class, eg:
class Map
{
public List<Block> Blocks;
public List<MovingPlatform> Platforms;
void LoadLevel(int level)
{
///
}
}
Now it makes the code cleaner, but also you now have a Map object you can pass around.
So for example to enable a MovingPlatform to have access to everything on a map, you just need to pass a reference to a map as a parameter to the MovingPlatform constructor which saves it away to a private field. Eg:
class MovingPlatform
{
Map _map;
public MovingPlatform(Map map, Vector2 position, ...)
{
_map = map;
}
public void Update(GameTime gameTime)
{
//move platform
//detect collision
//have access to all the blocks and other platforms on map
//_map.Blocks;
//_map.Platforms;
}
}
So in the map class LoadLevel() method you'd pass in 'this' as the first parameter of the MovingPlatform constructor:
class Map
{
void LoadLevel(int level)
{
//Vertical Moving Platform
if (Levels[level][y, x] == '=')
{
Platforms.Add(new MovingPlatform(this, ...));
}
}
}
You can add other map specific things (even a reference to the player?) to the Map class and the MovingPlatforms will automatically have access to them.
This is called Dependency Injection. Your MovingPlatform class has a dependency on the map and you are injecting that dependency in through the constructor.
Edit:
For collision you could add a Bounds property to Tile and MovingPlatform class. That makes it easier to get the current rectangle bounds of them. Also an IntersectsTile method in MovingPlatform that will return true if the platform intersects a specified tile.
class Tile
{
Rectangle Bounds
{
get
{
return new Rectangle((int)Position.X, (int)Position.Y, tileWidth, tileHeight);
}
}
}
class MovingPlatform
{
Rectangle Bounds
{
get
{
return new Rectangle((int)Position.X, (int)Position.Y, platformWidth, platformHeight);
}
}
//returns true if this platform intersects the specified tile
bool IntersectsTile(Tile tile)
{
return Bounds.Intersects(tile.Bounds);
}
}
Then in the MovingPlatfom class in the Update method where the platform is moved each frame, check if there is a collision after the movement. If there is a collision then back out the movement and reverse the platform direction, so next frame it will move the opposite way.
class MovingPlatform
{
void Update(Tile[] collidableTiles)
{
Position += direction;
//test collision
bool isCollision = CollisionTest(collidableTiles);
if (isCollision)
{
//undo the movement and change direction
Position -= direction;
direction = newDirection;
}
}
//returns true if this platform intersects with any of the specified tiles
bool CollisionTest(Tile[] tiles)
{
foreach (Tile tile in tiles)
if (IntersectsTile(tile))
return true;
return false;
}
}
For the above to work you need to pass in a list of collidable tiles on the map to the MovingPlatform update. Call each MovingPlatform in the map class, passing it the list of collidable tiles.
class Map
{
List<Tile> _collidableTiles;
void Update(GameTime gameTime)
{
foreach (MovingPlatform platform in MovingPlatforms)
{
platform.Update(_collidableTiles);
}
}
}
That's one way of doing it. A better way though would be instead of passing in collidable tiles, get the MovingPlatform to grab tiles around it and test those.
class MovingPlatform
{
void Update()
{
Position += direction;
//test collision
Tile[] tiles = _map.GetNearbyTiles(Position);
foreach (Tile tile in tiles)
if (tile.IsSolid)
if (IntersectsTile(tile))
{
//undo the movement and change direction
Position -= direction;
direction = newDirection;
break;
}
}
}
So that way all tiles have an IsSolid property and any that are set to true will cause moving platforms to collide with them.
class Tile
{
bool IsSolid;
}

Related

Unity Tilemaps, Single Tile Collision detection for highlighting Tiles/Cells, strange behaviour

Ive been working for a few days on this problem, trying to create a square grid of tiles, using Unity Tilemaps seems to be the most efficient way, and it has a lot built in already.
I want to highlight the tiles around the player so that he will see what the legal moves are.
I use a second Tilemap for this, on top of the basemap(ground/grass).
The second Tilemap, the highlightsMap is made up of invisible Tiles, which will turn into highlighted Tiles when a Physics.SphereOverlap occurs, or a Physics2D.CircleAll
To detect every Tile, I have added a box collider on an empty object and I made a grid of these colliders on top of the Tilemap grid, so that:
Box Collider at Position (2,4,0) is exactly on top of Tile at Position(2,4,0)
This should be the most straightforward way to handle this, as you can change a Tile, using Tilemap.SetTile(Vector3Int Pos, Tile tile)
The problem is very strange however. The colliders have the correct positional values to be able to reference the tiles exactly underneath them, just through that position data. As explained above, and I have double checked this, the collider empties have the exact same position as the tiles underneath them, no conversion is needed.
The problem is that the tiles are not highlighting around the player as expected, instead a few next to the player are highlighted and others arent, the Physics.OverlapSphere I am using only works on 3D Colliders, which is why I added them as another grid on top of everything.
Using Physics2D.CircleAll, unfortunately does not detect any 2D colliders on the tiles themselves(Sprite, or Grid), not sure if that is intended to work like that anyway.
Collision Grid Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
public class scr_ColliderGrid : MonoBehaviour
{
public GameObject eCollider;
public Tile invisibleTile;
public Tile highlightTile;
public Tilemap highlightMap;
public int xSize, ySize;
private Vector3Int[] gridArray; // Tilemaps use Vector3Int BUT only use (X, Y, 0) !!!!!!!
private int xC = 0, yC = 0, i = 0;
private Tile[] tiles;
private Vector3Int previous;
private void Start()
{
gridArray = new Vector3Int[xSize * ySize];
tiles = new Tile[xSize * ySize];
GenerateCollisionGrid();
}
private void GenerateCollisionGrid()
{
for (xC = 0; xC < xSize; xC++)
{
for (yC = 0; yC < ySize; yC++)
{
Vector3Int newPos = new Vector3Int(xC, yC, 0); // 2, 4, 0
Vector3Int newColPos = new Vector3Int(xC, yC, 0); // 2, 4, 0 //This used to be different values, but now they are exactly the same.
if (invisibleTile != null)
{
Tile tile = Instantiate(invisibleTile, newPos, Quaternion.identity, transform);
tiles[i] = tile;
GameObject col = Instantiate(eCollider, newColPos, Quaternion.identity, transform);
}
gridArray[i] = newPos;
i++;
}
}
highlightMap.SetTiles(gridArray, tiles);
highlightMap.SetTile(new Vector3Int(2,4,0), highlightTile); // 2,4,0 //Test to see if positions are the same. (collider and tiles)
}
Player Highlight Legal Moves Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
public class scr_TankMoves : MonoBehaviour
{
public Tile highlightTile;
public TileBase[] highlightTiles;
public Tilemap highlightMap;
public int maxMoveTiles;
public bool highlight;
private Vector3Int previous, previousLeft, previousRight, previousForward, previousAft, previousVect;
private Vector3Int[] previousVectors;
void Start()
{
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
transform.localPosition = new Vector3(transform.localPosition.x,
transform.localPosition.y + 1,
transform.localPosition.z );
}
if (Input.GetKeyDown(KeyCode.S))
{
transform.localPosition = new Vector3(transform.localPosition.x,
transform.localPosition.y - 1,
transform.localPosition.z );
}
if (Input.GetKeyDown(KeyCode.D))
{
transform.localPosition = new Vector3(transform.localPosition.x + 1,
transform.localPosition.y,
transform.localPosition.z);
}
if (Input.GetKeyDown(KeyCode.A))
{
transform.localPosition = new Vector3(transform.localPosition.x - 1,
transform.localPosition.y,
transform.localPosition.z);
}
}
void LateUpdate()
{
if (highlight)
HighlightMoves();
else EraseHighlights();
}
private void HighlightMoves()
{
previousVectors = new Vector3Int[1];
highlightMap.SetTiles(previousVectors, null);
int tMax = 0;
Collider[] legalTiles = Physics.OverlapSphere(transform.position, maxMoveTiles/2);
previousVectors = new Vector3Int[legalTiles.Length];
foreach (Collider col in legalTiles)
{
//Vector3 conversionVector = new Vector3(col.transform.localPosition.x, col.transform.localPosition.y, col.transform.localPosition.z);
Vector3Int tileVector = Vector3Int.FloorToInt(col.transform.position);
previousVectors[tMax] = tileVector;
tMax++;
}
highlightMap.SetTiles(previousVectors, highlightTiles);
}
private void EraseHighlights()
{
//highlightMap.SetTile(previousForward, null);
//highlightMap.SetTile(previousAft, null);
//highlightMap.SetTile(previous, null);
//highlightMap.SetTile(previousRight, null);
//highlightMap.SetTile(previousLeft, null);
highlightMap.SetTiles(previousVectors, null);
}
private void OnDrawGizmos()
{
Gizmos.DrawWireSphere(transform.position, maxMoveTiles/2);
}
}
}
If you open a new 3D Project in Unity and setup a grid with a Tilemap, called highlightMap in my code example, you should be able to recreate this exactly, using the 2 scripts.
Everything is oriented in 2D, this means x+ is Right y+ is forward and z+ is up/dephth (unused by tilemaps).
The empty Collider prefab I have is an empty GO with pos 0,0,0. it has another empty GO Child, which has the Box Collider Component, and the transform value of this child is 0.5,0.5,0.5, so that the Collider is centered on top of each tile.
This is after 0 moves, so just pressed Play:
This is after 1 move forward, (y+1):
Instead of going by colliders at all afaik you could also just use GridLayout.WorldToCell like e.g.
Tilemap yourTileMap;
// see https://docs.unity3d.com/ScriptReference/Tilemaps.Tilemap-layoutGrid.html
var grid = yourTileMap.layoutGrid;
// The "Grid" implements "GridLayout"
// see https://docs.unity3d.com/ScriptReference/Grid.html
var currentCellPos = grid.WorldToCell(transform.position);
var currentCell = yourTileMap.GetTile(currentCellPos);
Then if you need to get multiple tiles I would probably go the "lazy/stupid" way and just do something like e.g.
var currentCellPos = grid.WorldToCell(transform.position);
var currentCell = yourTileMap.GetTile(currentCellPos);
var leftCellPos = grid.WorldToCell(transform.position - transform.right * someRange);
var leftCell = yourTileMap.GetTile(leftCellPos);
var rightCellPos = grid.WorldToCell(transform.position + transform.right * someRange);
var rightCell = yourTileMap.GetTile(rightCellPos);
var frontCellPos = grid.WorldToCell(transform.position + transform.forward * someRange);
var frontCell = yourTileMap.GetTile(frontCellPos);
var backCellPos = grid.WorldToCell(transform.position - transform.forward * someRange);
var backCell = yourTileMap.GetTile(backCellPos);
highlightTiles[tMax] = Instantiate(highlightTile, col.transform.parent.position, Quaternion.identity, transform);
I dont know why, but apparently I didnt instantiate the tiles themselves into an Array of tiles, so everytime I was ''placing'' them on the grid, it was actually placing an empty array.
Now its fixed! Just need to find a nice way to erase all after a move and im set!

OverlapSphere centering incorrectly

I am attempting to use an overlapSphere to determine whether a tile within a tile map contains a game object. Then, if the tile is not already occupied, a new game object will be spawned in that tile (if certain other conditions are met). I'm trying to prevent game objects from overlapping with one another. However, I am having trouble centering the spheres about the right location.
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
Vector3 tilePos = tileMap.GetCellCenterWorld(new Vector3Int(x, y, 0));
if (terrainMap[x, y] == blocked)
{
bool spawn = detectOverlap(tilePos, obstCheckRad);
if (spawn)
{
GameObject obstacle = Instantiate(obstaclePrefab) as GameObject;
obstacle.transform.position = tilePos;
obstacle.transform.eulerAngles = new Vector3(0, 0, (Random.Range(0, 359)));
}
}
}
}
}
public bool detectOverlap (Vector3 center, float radius)
{
Collider[] collider = Physics.OverlapSphere(center, radius);
if (collider.Length == 0)
{
return true;
}
else
{
return false;
}
}
I am using "GetCellCenterWorld" to as the center of my overlapSpheres, however they always seem to originate in the bottom left corner of the map, causing this region of the map to be completely empty, and objects elswhere in the map to still overlap with one another. (see image)
My confusion is based around the fact that I am using the same vector3 (tilePos) to place down the game objects. The objects are all moved to the correct position, however, the overlapSpheres all stay at the bottom left.

Tiled Object Layer Position drawing to the wrong spot

I know this question is very specific to those familiar with both tiled and monogame, so I'll try to be a thorough as possible. I've been working on this issue for 15+ hours and am desperate.
I'm trying to draw sprites to the screen using Tiled's object layer. I've created a new object layer, drew some rectangles to it, and added a custom property which I reference in my LoadContent method.
Here is my LoadContent Method:
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
mymap = Content.Load<TiledMap>("Misc/newmap");
enemyShip = Content.Load<Texture2D>("Enemies/enemyship");
//TiledMapObject[] islands = mymap.GetLayer<TiledMapObjectLayer>("Islands").Objects;
ship_sprite = Content.Load<Texture2D>("character/wooden_ship");
Texture2D texture = Content.Load<Texture2D>("character/anima2");
animatedSprite = new AnimatedSprite(texture, 1, 2);
font = Content.Load<SpriteFont>("Misc/londrinasolid");
Song song = Content.Load<Song>("Music/Chad_Crouch_-_Stellars_Jay");
MediaPlayer.Volume = 0.7F;
MediaPlayer.Play(song);
TiledMapObject[] littleBoats = mymap.GetLayer<TiledMapObjectLayer>("SmallBoats").Objects;
foreach (var en in littleBoats)
{
string type;
en.Properties.TryGetValue("type", out type);
if (type == "smallboat")
{
Enemy.enemies.Add(new SmallBoat(en.Position));
}
}
// TODO: use this.Content to load your game content here
}
I've created a TiledMapObject Array which takes the items from the tiledmap object layer. It also checks the custom property name. If it it's "type", which you can see labeled in the bottom lefthand corner of the tiledmap picture, it takes that object and adds it to the array at the position on the tiledmap (at least it's supposed to).
In the draw method, I create a temporary Texture2D variable which references the aforementioned enemy list (which is now filled with two elements from the tiled map). If type Smallboat, it then draws the sprite I want to draw, which it does, just at the wrong position. Here is the relevant draw method:
foreach (Enemy en in Enemy.enemies)
{
Texture2D spritetoDraw;
int rad;
if (en.GetType() == typeof(SmallBoat))
{
rad = 16;
spritetoDraw = enemyShip;
}
else
{
rad = 30;
spritetoDraw = ship_sprite;
}
spriteBatch.Draw(spritetoDraw, new Vector2(en.Position.X - rad, en.Position.Y - rad), Color.White);
}
And here is the starting position of the enemy boat once I run, it's slightly offset from the origin 0,0 because in the draw method I subtract the radius:
Here is my Enemy class, with the SmallBoat child class at the bottom. I've created a new list which should contain the objects from the tiled layer once they are added in the LoadContent method in my main file. I've checked that the objects are being added by using Enemy.Remove(), and they are, so I know that at least the program recognizes the objects in the tiled file.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace my_first_game
{
class Enemy
{
private Vector2 position;
protected int radius;
float circle = MathHelper.Pi * 2;
public float rotationAngle;
protected int health;
public static List<Enemy> enemies = new List<Enemy>();
public int Radius { get { return radius; } }
public Vector2 Position { get { return position; }}
public int Health { get { return health; } set { value = health; } }
public Enemy(Vector2 newpos)
{
newpos = position;
}
public void Update(GameTime gametime, Vector2 playerPos)
{
rotationAngle = circle % 2;
double dt = gametime.ElapsedGameTime.TotalSeconds;
Vector2 direction = new Vector2((float)Math.Cos(rotationAngle), (float)Math.Sin(rotationAngle));
Vector2 movedir = Position - playerPos;
movedir.Normalize();
position -= movedir;
}
}
class SmallBoat : Enemy
{
public SmallBoat(Vector2 newpos) : base(newpos)
{
//radius = 50;
}
}
}
What I've tried:
1). commented out all update methods and ran, nothing changes except the ship's don't move.
2). Drawn new rectangles, erased old ones, deleted and created object layers.
3). if I remove a rectangle from the object layer, there are less boats drawn to the screen once the game is running. Therefore I know that the enemies are being drawn to the screen, just not at the right position.
4). Changed the coordinates to a hardcoded fixed number in the draw method instead of en.position.X and en.position.Y. This changes the starting position to the desired location, but it's hardcoded and doesn't solve the problem.
5). Rearranged elements in the loadcontent method and draw method, to no avail.
It seems that the list is being properly referenced and that the issue is that for some reason the coordinates on the tiled map aren't transfering over. Much of my code is based off of a Udemy Monogame tutorial, and the loadcontent method and draw method are basically the same.
Any and all help is warmly welcomed. Please let me know if there's any other information I can provide.
Edit: Here is the entire draw method.
protected override void Draw(GameTime gameTime)
{
controller.controller_update(gameTime);
spriteBatch.Begin();
if (controller.ingame == false)
{
spriteBatch.DrawString(font, "Welcome to Thallasaphobia. \n press Enter to Begin", new Vector2(100, 100), Color.White);
}
spriteBatch.End();
if (controller.ingame == true)
{
GraphicsDevice.Clear(Color.Black);
mapRenderer.Draw(mymap, cam.GetViewMatrix());
spriteBatch.Begin(transformMatrix: cam.GetViewMatrix());
animatedSprite.Draw(spriteBatch, new Vector2(480, 350));
//create a new rectangle around the ship_sprite
Rectangle ship_rectangle = new Rectangle(0, 0, ship_sprite.Width, ship_sprite.Height);
Vector2 origin = new Vector2(ship_sprite.Width / 2, ship_sprite.Height + 15);
foreach (Enemy en in Enemy.enemies)
{
Texture2D spritetoDraw;
int rad;
if (en.GetType() == typeof(SmallBoat))
{
rad = 16;
spritetoDraw = enemyShip;
}
else
{
rad = 30;
spritetoDraw = ship_sprite;
}
spriteBatch.Draw(spritetoDraw, new Vector2(en.Position.X - rad, en.Position.Y - rad), Color.White);
}
spriteBatch.Draw(ship_sprite, ship.Position, ship_rectangle, Color.White, ship.rotationAngle + MathHelper.Pi/2, origin, 1.0f, SpriteEffects.None, 1);
}
spriteBatch.End();
base.Draw(gameTime);
}
Although I don't really have a means of testing this, I believe the problem is in your constructor. You currently have this code:
public Enemy(Vector2 newpos)
{
newpos = position;
}
You should actually have:
public Enemy(Vector2 newpos)
{
position = newpos;
}
Since "position" is the property of the Enemy class, THAT is the value we want updated, not newpos, which is just a parameter. So that would explain why your enemy is starting at the origin, because its position is never being initially set.

XNA doesn't jump of the platform

I am working on my game using XNA. I have found a series of tutorials by Oyyou from YouTube and have gone to the collision tutorial so far. I have successfully processed gravity, movement and animation. But I have a problem with collision. The spawn point of my character is up in the air.
The first value is my bool hasJumped. And the second is y velocity. It is accelerating by 0.25.
The character is falling down slowly accelerating. All four platforms are made the same way. All four are in the list and I don't make anything to work with any of them particularly.
When I land on the lowest (ground) I get the parameters I need. hasJumped is false and velocity.Y is 0.
But when I'm on one of three non-ground platforms results are not as I want them. The velocity is 0.25 even though I am making it 0. And hasJumped doesn't become true because my velocity is a non-zero variable. Unluckily I can't post images so I can't show it. But I will post my code:
this is the maingame class where I add players and platforms.
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
player = new Animation(Content.Load<Texture2D>("player"), Content.Load<Texture2D>("rect"), new Vector2(300 + 28, graphics.PreferredBackBufferHeight - 500), 47, 44, graphics.PreferredBackBufferHeight, graphics.PreferredBackBufferWidth);
platformList.Add(new Platform(Content.Load<Texture2D>("Crate"), new Vector2(300,graphics.PreferredBackBufferHeight-100), 80,50));
platformList.Add(new Platform(Content.Load<Texture2D>("Crate"), new Vector2(500, graphics.PreferredBackBufferHeight - 50), 80, 50));
platformList.Add(new Platform(Content.Load<Texture2D>("Crate"), new Vector2(700, graphics.PreferredBackBufferHeight - 60),80,50));
platformList.Add(new Platform(Content.Load<Texture2D>("Crate"), new Vector2(0, graphics.PreferredBackBufferHeight - 10), graphics.PreferredBackBufferWidth, 10));
// TODO: use this.Content to load your game content here
}
This is my Animation class:
class Animation
{
Texture2D texture;
Texture2D rectText;
public Rectangle rectangle;
public Rectangle collisionRect;
public Vector2 position;
Vector2 origin;
public Vector2 velocity;
int currentFrame;
int frameHeight;
int frameWidth;
int screenHeight;
int screenWidth;
float timer;
float interval = 60;
bool movingRight;
public bool hasJumped;
public Animation(Texture2D newTexture, Texture2D newRectText, Vector2 newPosition, int newHeight, int newWidth, int screenH, int screenW)
{
rectText = newRectText;
texture = newTexture;
position = newPosition;
frameHeight = newHeight;
frameWidth = newWidth;
screenHeight = screenH;
screenWidth = screenW;
hasJumped = true;
}
public void Update(GameTime gameTime)
{
rectangle = new Rectangle(currentFrame * frameWidth, 0, frameWidth, frameHeight);
collisionRect = new Rectangle((int)position.X-frameWidth/2, (int)position.Y - frameHeight/2, frameWidth, frameHeight);
origin = new Vector2(rectangle.Width / 2, rectangle.Height / 2);
position = position + velocity;
if (Keyboard.GetState().IsKeyDown(Keys.Right))
{
AnimateRight(gameTime);
velocity.X = 5;
movingRight = true;
}
else if (Keyboard.GetState().IsKeyDown(Keys.Left))
{
AnimateLeft(gameTime);
velocity.X = -5;
movingRight = false;
}
else
{
velocity.X = 0f;
if (movingRight)
currentFrame = 0;
else currentFrame = 4;
}
if(Keyboard.GetState().IsKeyDown(Keys.Space) && !hasJumped)
{
position.Y -= 5f;
velocity.Y = -10;
hasJumped = true;
}
if(hasJumped)
{
velocity.Y += 0.25f;
}
if(position.X<0+frameWidth/2)
{
position.X = 0 + frameWidth / 2;
}
else if (position.X>screenWidth-frameWidth/2)
{
position.X = screenWidth - frameWidth / 2;
}
}
public void AnimateRight(GameTime gameTime)
{
timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds/2;
if(timer>interval)
{
currentFrame++;
timer = 0;
if (currentFrame > 3)
{
currentFrame = 0;
}
}
}
public void AnimateLeft(GameTime gameTime)
{
timer += (float)gameTime.ElapsedGameTime.TotalMilliseconds / 2;
if (timer > interval)
{
currentFrame++;
timer = 0;
if (currentFrame > 7 || currentFrame < 4)
{
currentFrame = 4;
}
}
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture,position,rectangle,Color.White, 0f, origin, 1.0f, SpriteEffects.None, 0);
// spriteBatch.Draw(rectText,collisionRect,Color.White);
}
}}
The platform class is pretty simple:
class Platform
{
Texture2D texture;
Vector2 position;
public Rectangle rectangle;
public Platform(Texture2D newTexture, Vector2 newPosition, int platformWidth, int platformHeight)
{
texture = newTexture;
position = newPosition;
rectangle = new Rectangle((int)position.X, (int)position.Y, platformWidth, platformHeight);
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture, rectangle, Color.White);
}
}
and the collision method:
public static bool isOnTopOf(this Rectangle r1, Rectangle r2)
{
return (r1.Bottom >= r2.Top - 10 &&
r1.Bottom <= r2.Top + 10 &&
r1.Right >= r2.Left + 10 &&
r1.Left <= r2.Right - 10);
}
The problem is supposed to be here:
foreach (Platform a in platformList)
{
if (player.collisionRect.isOnTopOf(a.rectangle) && player.velocity.Y>=0)
{
player.hasJumped = false;
player.velocity.Y = 0f;
if(player.collisionRect.Bottom > a.rectangle.Top || player.collisionRect.Bottom - a.rectangle.Top <=10)
{
player.position.Y = a.rectangle.Y - 48 / 2;
player.hasJumped = false;
}
}
else
{
player.hasJumped = true;
}
}
But if I exclude the last else it won't fall of the platform.
Thank you for upvoting the question. I have added images.
Your problem is that when the user is on top of a platform that is not the lowest one, during the else when you check if the user is on a platform, then you explicitly set the hasJumped to true.
What you must do is in your foreach where you check your platforms, to order them by INCREASING Y-coordinate (very much depending on your camera settings) and break after you set hasJumped to false. This way you will avoid your issue:
foreach (Platform a in platformList.OrderByY())
{
if (player.collisionRect.isOnTopOf(a.rectangle) && player.velocity.Y>=0)
{
player.hasJumped = false;
player.velocity.Y = 0f;
if(player.collisionRect.Bottom > a.rectangle.Top || player.collisionRect.Bottom - a.rectangle.Top <=10)
{
player.position.Y = a.rectangle.Y - 48 / 2;
player.hasJumped = false;
}
break;
}
else
{
player.hasJumped = true;
}
}
Unfortunately, it is often the case with 3D development that we have to do some hacks to get the application to render correctly. This is especially the case when rendering a complex item with advanced transparencies. In your case however, I would advise to re-imagine the way you work with collisions.
Games development is not an easy task. Superficially, the constraints under which you are working enforce a less than ideal architecture. Everything in an XNA game revolves around the update loop and the ability to control which item gets updated first is so easy and tempting that people tend to use that. It is however generally a much better approach to try and extrapolate functionality in such a way that your models behave in an event-driven way. One way to achieve this is to split the update cycle of your game in the following phases:
Input Phase: Retrieve input from the user and handle them as events (these can be simple method calls on items in a method other than Update or a more complex but better method like using Rx)
Movement Phase: Develop some physical characteristics in your models (acceleration, velocity, weight, etc.) and letting them do the work of positioning, rotating on their own during the movement phase. This is the only thing that they should be doing in Update.
Collision Phase: Test for collision and then fire events on the objects to handle collision.
Cleanup Phase: Cleanup any problems, like moving inside a wall during the collision detection phase. or phasing out particles etc. This gets important when you have requirements to draw on exact pixels to avoid blurring etc.
The above is not necessarily the best way to do it, but it works. If you rely only on the update cycle you will very soon reach a point where the interdependencies between models and the requirement to have some events fired in order will be a nightmare to support. Enforcing an event driven mechanism will save you from a lot of scrapped work with a very small initial overhead.
My suggestion to use Rx may sound a little strange for an XNA game, but trust me, it is an awesome way to work with events. Finally, I would like to mention MonoGame, an excellent framework to develop games for Windows, Mac, Linux, Android, iPhone, Windows Phone, WinRT (and even more) if you do not already know it.

What I need to create this control for this game? (Like pipeline)

This is my first post at this section (XNA and game development). I'm trying to get something the below image. As you can see, there's a highway and inside of it, there'll be some objects moving (millisecond). I guess the streeth behavier is like a pipeline. When the highway loads an object, it appears at the beggining and it'll be moving through the highware until it arrives in the another extreme of the highway.
My main problem is, how can I do to move several objects only inside of the highway?
Thanks in advance.
You need a list of points and a list of sprites
class Path
{
List<Vector2> Points;
float[] Lengths;
Vector2[] Directions;
void Build()
{
Lengths = new float[Points.Count-1];
Directions = new float[Points.Count-1];
for (int i=0; i<Points.Count-1;i++)
{
Directions[i] = Points[i+1] - Points[i];
Lengths[i] = Directions[i].Length();
Directions[i].Normalize();
}
}
}
class Sprite
{
Vector2 Position;
float StagePos;
int StageIndex;
Path Path;
float Speed;
void Update(float Seconds)
{
if (StageIndex!=Path.Points.Count-1)
{
StagePos += Speed * Seconds;
while (StagePos>Path.Lengths[StageIndex])
{
StagePos -= Path.Lengths[StageIndex];
StageIndex++;
if (StageIndex == Path.Points.Count-1)
{
Position = Path.Points[StageIndex];
return;
}
}
Position = Path.Points[StageIndex] + Directions[StageIndex] * StagePos;
}
}
}

Categories