I'm trying to build a Shmup game with a tiled background, where I would like to make it procedure generated in the future.
But right now, I can't even make a static background. No matter what, I can't make the tiles fill the screen.
I'm using a tile of 64x64 pixels.
And here the code I'm using:
void Start()
{
m_screenHeight = 1408;
m_screenWidht = Camera.main.aspect * m_screenHeight;
float UnitsPerPixel = 1f / 100f;
Camera.main.orthographicSize = m_screenHeight / 2f * UnitsPerPixel;
m_horizontalTilesNumber = (int)Math.Floor(m_screenWidht / TileSize);
m_verticalTilesNumber = (int)Math.Floor(m_screenHeight / TileSize);
for (int i = 0; i < m_horizontalTilesNumber; i++)
{
for (int j = 0; j < m_verticalTilesNumber; j++)
{
Instantiate(Tile, Camera.main.ScreenToWorldPoint(new Vector3(TileSize * i, TileSize * j, 0)), Quaternion.identity);
}
}
}
Here is what It look likes:
Seems to me that I'm having some problem converting my pixel coordinates to Units, or something like this.
Any tips or directions here would be appreciated.
Try to use this code below:
/// <summary>
/// Tile prefab to fill background.
/// </summary>
[SerializeField]
public GameObject tilePrefab;
/// <summary>
/// Use this for initialization
/// </summary>
void Start()
{
if (tilePrefab.renderer == null)
{
Debug.LogError("There is no renderer available to fill background.");
}
// tile size.
Vector2 tileSize = tilePrefab.renderer.bounds.size;
// set camera to orthographic.
Camera mainCamera = Camera.main;
mainCamera.orthographic = true;
// columns and rows.
int columns = Mathf.CeilToInt(mainCamera.aspect * mainCamera.orthographicSize / tileSize.x);
int rows = Mathf.CeilToInt(mainCamera.orthographicSize / tileSize.y);
// from screen left side to screen right side, because camera is orthographic.
for (int c = -columns; c < columns; c++)
{
for (int r = -rows; r < rows; r++)
{
Vector2 position = new Vector2(c * tileSize.x + tileSize.x / 2, r * tileSize.y + tileSize.y / 2);
GameObject tile = Instantiate(tilePrefab, position, Quaternion.identity) as GameObject;
tile.transform.parent = transform;
}
}
}
You can find the whole demo project here: https://github.com/joaokucera/unity-procedural-tiled-background
Related
the idea is how to align objects like in photo below, what formula need to use for this?
Now I can only do it this way, through sin or cos
void Start()
{
for (int i = 0; i < 50; i++)
characters.Add(Instantiate(characterPrefab, charactersParent));
}
// Update is called once per frame
void Update()
{
for (int i = 0; i < characters.Count; i++)
{
float x = Mathf.Cos(i * verticalLines * Mathf.PI / characters.Count) * horizontalRadius;
characters[i].GetComponent<RectTransform>().anchoredPosition = new Vector3(x, -i * (1920f / characters.Count), 0);
}
}
In snake your rewards/bug eated are added on the opposite direction that the last body-part is facing.
The problem here is how you stablish your current snake movement, to get the opposite direction.
Let's guess that your movement is x += 1, then your opposite position will be your X position on your characters[characters.Count] and go on the opposite direction x -=1.
I have run into this issue where I have a GameObject, for example size 1x3 - and the gameobject is only recognized for being on a tile because it's placed there and given coordinates in the inspector (for example 5 x, 0 y, 6 z). The problem being that the code only detects that there is a GameObject on one tile, the middle tile of the grid code.
I need to make sure the other tiles the GameObject is covering is aware that there is a GameObject there, so I can set them all to for example that they're not walkable on - or that you can use them as cover by being next to them. But so far the code only registers 1 tile of the GameObject - therein lies the problem.
Thing is, I don't know where to start. I have some ideas, maybe do a loop to check tiles next to them if there is a gameobject but then the code won't know if something is on that tile and the loop would also be quite heavy on performance - unless I do that loop on Awake or Start or something similar.
Can anyone point me in the right direction? Is there some built-in thing in Unity that can detect if something is above the specific tile I created?
It's an own gridsystem for the record to support 3D, so I'm not using the built-in tilemaps system for 2D games that Unity offers these days.
Unity Version 2017.3
Down below is Gridbase.cs - this handles the world and spawns all nodes (tiles)
public class GridBase : MonoBehaviour
{
//Grid Scale
public int sizeX = 32;
//amount of floors/levels, Y is up
public int sizeY = 3;
public int sizeZ = 32;
public float scaleXZ = 1;
// character scale
public float scaleY = 2.3f;
public Node[,,] grid;
public List<YLevels> yLevels = new List<YLevels>();
public bool debugNode = true;
public Material debugMaterial;
GameObject debugNodeObj;
void Start()
{
InitPhase();
}
public void InitPhase()
{
if (debugNode)
debugNodeObj = WorldNode();
//debug check all values for the grid.
Check();
//Spawn Grid Function
CreateGrid();
GameManager.singleton.Init();
}
void Check()
{
if(sizeX == 0)
{
Debug.Log("Size x is 0, assigning min");
sizeX = 16;
}
if(sizeY == 0)
{
Debug.Log("Size y is 0, assigning min");
sizeY = 1;
}
if (sizeZ == 0)
{
Debug.Log("Size z is 0, assigning min");
sizeX = 1;
}
if (scaleXZ == 0)
{
Debug.Log("ScaleXZ is 0, assigning min");
scaleXZ = 1;
}
if (scaleY == 0)
{
Debug.Log("ScaleY is 0, assigning min");
scaleY = 2;
}
}
void CreateGrid()
{
grid = new Node[sizeX, sizeY, sizeZ];
for (int y = 0; y < sizeY; y++)
{
YLevels ylvl = new YLevels();
ylvl.nodeParent = new GameObject();
ylvl.nodeParent.name = "Level" + y.ToString();
ylvl.y = y;
yLevels.Add(ylvl);
//Creating Collision for all nodes)
CreateCollision(y);
for (int x = 0; x < sizeX; x++)
{
for (int z = 0; z < sizeZ; z++)
{
Node n = new Node();
n.x = x;
n.y = y;
n.z = z;
n.isWalkable = true;
if(debugNode)
{
Vector3 targetPosition = WorldCoordinatesFromNode(x, y, z);
GameObject go = Instantiate(debugNodeObj,
targetPosition,
Quaternion.identity ) as GameObject;
go.transform.parent = ylvl.nodeParent.transform;
}
grid[x, y, z] = n;
}
}
}
}
void CreateCollision(int y)
{
YLevels lvl = yLevels[y];
GameObject go = new GameObject();
BoxCollider box = go.AddComponent<BoxCollider>();
// Creates a box collider that has the whole size of the grid + a little bit more
box.size = new Vector3(sizeX * scaleXZ + (scaleXZ * 2),
0.2f,
sizeZ * scaleXZ + (scaleXZ * 2));
//Spawn box collider in center
box.transform.position = new Vector3((sizeX * scaleXZ) * .5f - (scaleXZ * .5f),
y * scaleY,
(sizeZ * scaleXZ) * 0.5f - (scaleXZ * .5f));
lvl.CollisionObj = go;
lvl.CollisionObj.name = "lvl " + y + " collision";
}
public Node GetNode(int x, int y, int z)
{
x = Mathf.Clamp(x, 0, sizeX - 1);
y = Mathf.Clamp(y, 0, sizeY - 1);
z = Mathf.Clamp(z, 0, sizeZ - 1);
return grid[x, y, z];
}
//get World Cordinates from any Node
public Vector3 WorldCoordinatesFromNode(int x, int y, int z)
{
Vector3 r = Vector3.zero;
r.x = x * scaleXZ;
r.y = y * scaleY;
r.z = z * scaleXZ;
return r;
}
GameObject WorldNode()
{
GameObject go = new GameObject();
GameObject quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
Destroy(quad.GetComponent<Collider>());
quad.transform.parent = go.transform;
quad.transform.localPosition = Vector3.zero;
quad.transform.localEulerAngles = new Vector3(90, 0, 0);
quad.transform.localScale = Vector3.one * 0.95f;
quad.GetComponentInChildren<MeshRenderer>().material = debugMaterial;
return go;
}
public static GridBase singleton;
private void Awake()
{
singleton = this;
}
}
[System.Serializable]
public class YLevels
{
public int y;
public GameObject nodeParent;
public GameObject CollisionObj;
}
And this is the Node.cs file
public class Node
{
//Node's position in the grid
public int x;
public int y;
public int z;
//Node's costs for pathfinding purposes
public float hCost;
public float gCost;
public float fCost
{
get //the fCost is the gCost+hCost so we can get it directly this way
{
return gCost + hCost;
}
}
public Node parentNode;
public bool isWalkable = true;
//Reference to the world object so we can have the world position of the node among other things
public GameObject worldObject;
//Types of nodes we can have, we will use this later on a case by case examples
public NodeType nodeType;
public enum NodeType
{
ground,
air
}
}
this is the GridBase code pastebin.com/gazu9jPR that spawns the nodes (tiles) and this is the node script that triggers whenever gridbase references to Node class - pastebin.com/jk25n6ee
You can use trigger colliders at the place the gameobjects will be created on. You'll need a seperate collider for each grid cell. Make the gameobject with the collider the child of the grid cell (I don't exactly know your hierarchy, so I'll just keep guessing). Then, when the collider detects the gameobject, let the corresponding cell know that it is occupied. Hope this helps.
All the tiles need to have colliders. Also the object you place on the grid needs to have a collider(tick as trigger) which is big enough to overlap with tiles' colliders. Then you can use that to check how many tiles are covered by that object. The way you can do this is by using Collider.bounds which is of type Bounds. Bounds object has a method called Contains which you can pass in the tile's center point and see if it's covered by the object.
I'm trying to save the coordinates of placed tiles after I use a x and y loop to place them. What I would like to do is to take the coordinates of those tiles and place them in a 2d array, or save them somewhere so that when the level loads with the tiles, the coordinates of those tiles will be saved and I can use them later. I'm trying to use those points for the player, so he can travel from point to point, one step at a time.
I'm not sure if im going about it correctly and would like to be able to print the coordinates in the console to see if they were saved.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MapGenerator : MonoBehaviour
{
public Transform tilePrefab;
public Vector2 mapSize;
[Range(0, 1)]
public float tileOutline;
public float[] tilePointX;
public Vector3[,] positionArray;
public Vector3 tilePosition;
void Start()
{
MapGeneratorMethod();
}
public void MapGeneratorMethod()
{
for (int x = 0; x < mapSize.x; x++)
{
for (int y = 0; y < mapSize.y; y++)
{
Debug.Log("TEST");
tilePosition = new Vector3(-mapSize.x / 2 + 0.5f + x, 0, -mapSize.y / 2 + 0.5f + y);
//getposition of tile = position array
Transform newTile = Instantiate(tilePrefab, tilePosition, Quaternion.Euler(Vector3.right * 90)) as Transform;
newTile.localScale = Vector3.one * (1 - tileOutline);
}
}
}
public void findTilePoint()
{
for (int x = 0; x < mapSize.x; x++)
{
for (int y = 0; y < mapSize.y; y++)
{
tilePosition = new Vector3(-mapSize.x / 2 + 0.5f + x, 0, -mapSize.y / 2 + 0.5f + y);
positionArray[x,y] = tilePosition;
Debug.Log(x);
}
}
// Debug.Log(x);
}
}
I would recommend you do this in a slightly different way, instead of storing the positions of the tiles in a 2D array I would stores references to the transforms of the tiles in a 2D array. i.e.
public float tileOutline;
public float[] tilePointX;
public Transform[,] tilesArray;
public Vector3 tilePosition;
void Start()
{
MapGeneratorMethod();
}
public void MapGeneratorMethod()
{
tilesArray = new transform[mapSize.x, mapSize.y];
for (int x = 0; x < mapSize.x; x++)
{
for (int y = 0; y < mapSize.y; y++)
{
Debug.Log("TEST");
tilePosition = new Vector3(-mapSize.x / 2 + 0.5f + x, 0, -mapSize.y / 2 + 0.5f + y);
//getposition of tile = position array
Transform newTile = Instantiate(tilePrefab, tilePosition, Quaternion.Euler(Vector3.right * 90)) as Transform;
newTile.localScale = Vector3.one * (1 - tileOutline);
tilesArray[x,y] = newTile;
}
}
By doing it this way you still are able to get the position of the tiles using tilesArray[x,y].GetPosition() but you will also be able to get any component connected to the tile whenever you need. also note that i added the line
tilesArray = new transform[mapSize.x, mapSize.y];
at the beginning of the function. you should really initialise the array this way before using it. As a final note you are able to pass a vector2 or vector3 directly to the debug.log as follows.
public void findTilePoint()
{
for (int x = 0; x < mapSize.x; x++)
{
for (int y = 0; y < mapSize.y; y++)
{
vector3 tilePosition = tilesArray[x,y].GetPosition()
Debug.Log(tilePosition);
}
}
// Debug.Log(x);
}
I created a series of sphere clones in my game. After that I adapted the scale so that they appear smaller. However, now there is a gap between these spheres ... and I would have to change the position of this instatiate game objects. I changed my code already exactly at this position but nothing happens. So please I need your help! How can I do this? I would have very small spheres which are located near together.
Here the code:
using UnityEngine;
using System.Collections;
public class SineWave : MonoBehaviour {
private GameObject plotPointObject;
private int numberOfPoints= 100;
private float animSpeed =1.0f;
private float scaleInputRange = 8*Mathf.PI; // scale number from [0 to 99] to [0 to 2Pi] //Zahl vor Mathf, Anzahl Bön
private float scaleResult = 2.5f; // Y Achse Range
public bool animate = true;
GameObject[] plotPoints;
// Use this for initialization
void Start () {
if (plotPointObject == null) //if user did not fill in a game object to use for the plot points
plotPointObject = GameObject.CreatePrimitive(PrimitiveType.Sphere); //create a sphere
//add Material to the spheres , load material in the folder Resources/Materials
Material myMaterial = Resources.Load("Materials/green", typeof(Material)) as Material;
plotPointObject.GetComponent<MeshRenderer> ().material = myMaterial;
//change the scale of the spheres
//plotPointObject.transform.localScale = Vector3.one * 0.5f ;
plotPointObject.transform.localScale -= new Vector3(0.5f,0.5f,0.5f);
plotPoints = new GameObject[numberOfPoints]; //creat an array of 100 points.
//plotPointObject.GetComponent<MeshRenderer> ().material =Material.Load("blue") as Material
//plotPointObject.transform.localScale -= new Vector3 (0.5F, 0.5F, 0.5F); //neu: change the scale of the spheres
for (int i = 0; i < numberOfPoints; i++)
{
plotPoints[i] = (GameObject)GameObject.Instantiate(plotPointObject, new Vector3(i -
(numberOfPoints/2), 0, 0), Quaternion.identity); //this specifies
what object to create, where to place it and how to orient it
}
//we now have an array of 100 points- your should see them in the hierarchy when you hit play
plotPointObject.SetActive(false); //hide the original
}
Thank you already in advance!
Edit:
As I said in the comment I achieved now to place my spheres without a gap in between. However, as soon as I animate my spheres (with a sine wave) there is still that gap between the spheres. How can I adapt this? Should I copy the code of the Start function in the Update function?
I would be very happy to get some help. Thank you very much!
enter code here void Update()
{
for (int i = 0; i < numberOfPoints; i++)
{
float functionXvalue = i * scaleInputRange / numberOfPoints; // scale number from [0 to 99] to [0 to 2Pi]
if (animate)
{
functionXvalue += Time.time * animSpeed;
}
plotPoints[i].transform.position = new Vector3(i - (numberOfPoints/2), ComputeFunction(functionXvalue) * scaleResult, 0);
//print (plotPointObject.GetComponent<MeshRenderer> ().bounds.size.x);
// put the position information of sphere clone 50 in a vector3 named posSphere
posSphere = plotPoints [50].transform.position;
}
//print position of sphere 50 in console
//print (posSphere);
}
float ComputeFunction(float x)
{
return Mathf.Sin(x);
}
}
I think you could make the Barış solution.
For each new object that you are instantiating, you will set his position to the lasted instantiated position adding the size of the object itself, or whatever distance that you want they have from each other.
var initialPosition = 0;
var distanceFromEachOther = 20;
for (int i = 0; i < numberOfPoints; i++) {
var newPos = new Vector3(initialPosition + (i * distanceFromEachOther), 0, 0);
plotPoints[i] = (GameObject)GameObject.Instantiate(plotPointObject, newPos, Quaternion.identity);
}
That will make a gap between the spheres at X pivot, depending on their size. Change the distanceFromEachOther var, adjusting for your needs.
You could also get the object distance with plotPointObject.GetComponent<MeshRenderer>().bounds.size, so distanceFromEachOther could be, for example distanceFromEachOther = plotPointObject.GetComponent<MeshRenderer>().bounds.size.x + 5. So then you will have the objects with a perfectly distance of 5 from each other.
give this a try:
Transform objectToSpawn;
for (int i = 0; i < numberOfPoints; i++)
{
float someX = 200;
float someY = 200;
Transform t = Instantiate(objectToSpawn, new Vector3(i -(numberOfPoints/2), 0, 0), Quaternion.identity) as Transform;
plotPoints[i] = t.gameObject;
t.position = new Vector(someX, someY);
}
The problem that I am having is wrapping my brain around how I could use a single png called Test Map.png:
This has a black border around the screen and smaller stepping blocks for the player to test the collision. I have the gravity working by using a player class and the main class Game1.cs to draw and update the game. I use this ball:
This is my sprite that I move around the screen.
Here is the player.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace Gravity_Test_V2
{
class Player
{
public Texture2D Texture;
public Vector2 Velocity;
public Vector2 Position;
public float ground;
private float Speed;
private Rectangle screenBound;
public bool isJumping; //are we jumping or not
public bool goingUp; //if going up or not
public float initialVelocity; //initial velocity
private float jumpU; //How high the player can jump
private float g; //gravity
public float t; //time
private KeyboardState prevKB;
public Player(Texture2D Texture, Vector2 Position, float Speed, Rectangle screenBound)
{
this.Texture = Texture;
this.Position = Position;
ground = Position.Y;
this.Speed = Speed;
this.screenBound = screenBound;
Velocity = Vector2.Zero;
isJumping = goingUp = true;
jumpU = 2.5f;
g = -9.8f;
t = 0;
}
public void Update(GameTime gameTime)
{
Position.X += (Velocity.X * Speed);
//Set the Y position to be subtracted so that the upward movement would be done by decreasing the Y value
Position.Y -= (Velocity.Y * Speed);
goingUp = (Velocity.Y > 0);
// TODO: Add your update logic here
if (isJumping == true)
{
//motion equation using velocity: v = u + at
Velocity.Y = (float)(initialVelocity + (g * t));
//Increase the timer
t += (float)gameTime.ElapsedGameTime.TotalSeconds;
}
if (isJumping == true && Position.Y > screenBound.Height - Texture.Height)
{
Position.Y = ground = screenBound.Height - Texture.Height;
Velocity.Y = 0;
isJumping = false;
t = 0;
}
if (Position.X < 0)
{
//if Texture touches left side of the screen, set the position to zero and the velocity to zero.
Position.X = 0;
Velocity.X = 0;
}
else if (Position.X + Texture.Width > screenBound.Width)
{
//if Texture touches left side of the screen, set the position to zero and the velocity to zero.
Position.X = screenBound.Width - Texture.Width;
Velocity.X = 0;
}
if (Position.Y < 0)
{
//if the Texture touches the top of the screen, reset the timer and set the initial velocity to zero.
Position.Y = 0;
t = 0;
initialVelocity = 0;
}
}
public void Input(KeyboardState keyState)
{
if (keyState.IsKeyDown(Keys.Space) && (isJumping == false || Position.Y == ground))
{
isJumping = true;
initialVelocity = jumpU;
}
if (keyState.IsKeyDown(Keys.Left) && !keyState.IsKeyDown(Keys.Right))
{
if (Velocity.X > -1.0f)
{
Velocity.X -= (1.0f / 10);
}
else
{
Velocity.X = -1.0f;
}
}
else if (!keyState.IsKeyDown(Keys.Left) && keyState.IsKeyDown(Keys.Right))
{
if (Velocity.X < 1.0f)
{
Velocity.X += (1.0f / 10);
}
else
{
Velocity.X = 1.0f;
}
}
else
{
if (Velocity.X > 0.05 || Velocity.X < -0.05)
Velocity.X *= 0.70f;
else
Velocity.X = 0;
}
prevKB = keyState;
}
public void Fall()
{
t = 0;
initialVelocity = 0;
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(Texture, new Rectangle((int)Position.X, (int)Position.Y, Texture.Width, Texture.Height), Color.White);
}
}
}
Is there some simple way to make it so the player can collide with the Test Map.png while its inside the Test Map's texture?
EDIT: 1/21/2014 9 AM 'ish'
Level One:
EDIT: 1/21/2014 10:27
I have used a pixle based system to test to see if the player colides with an object but I try to sepreate the object from the play into classes and it will stop working. I mixed both my movment and collision projects together to try and make it work. I took player.cs (witch I have not changed) and added the pixel based collision into the Game1.cs I need to know how to make the player, witch is being controlled by the player.cs class, be seen by the Game1.cs class and used while being called upon by the player.cs class.
Note* I have also changed it so that the game would be using the falling triangles supplied by the pixel based system. I will add the test image when I am able to make this work.
At the moment The player can move and jump but is not reconsidered as colliding.
EDIT: 1/21/2014 10:34
I use 2 projects:
Collision:
http://xbox.create.msdn.com/en-US/education/catalog/tutorial/collision_2d_perpixel
Movment System:
http://gamepopper.co.uk/academic-projects/2012-2/jumping-platformer-example/
I have mixed them up and used pices of them to try and make my own platform.
Game1.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace Collision_Test
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
KeyboardState prevKB;
Player player;
SpriteFont font;
Texture2D personTexture;
Texture2D blockTexture;
// The color data for the images; used for per pixel collision
Color[] personTextureData;
Color[] blockTextureData;
Vector2 personPosition;
const int PersonMoveSpeed = 5;
public static int screenWidth = 800;
public static int screenHeight = 500;
// Blocks
List<Vector2> blockPositions = new List<Vector2>();
float BlockSpawnProbability = 0.01f;
const int BlockFallSpeed = 1;
Random random = new Random();
// For when a collision is detected
bool personHit = false;
// The sub-rectangle of the drawable area which should be visible on all TVs
Rectangle safeBounds;
// Percentage of the screen on every side is the safe area
const float SafeAreaPortion = 0.05f;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
this.graphics.PreferredBackBufferWidth = screenWidth;
this.graphics.PreferredBackBufferHeight = screenHeight;
this.graphics.ApplyChanges();
}
protected override void Initialize()
{
base.Initialize();
// Calculate safe bounds based on current resolution
Viewport viewport = graphics.GraphicsDevice.Viewport;
safeBounds = new Rectangle(
(int)(viewport.Width * SafeAreaPortion),
(int)(viewport.Height * SafeAreaPortion),
(int)(viewport.Width * (1 - 2 * SafeAreaPortion)),
(int)(viewport.Height * (1 - 2 * SafeAreaPortion)));
// Start the player in the center along the bottom of the screen
personPosition.X = (safeBounds.Width - personTexture.Width) / 2;
personPosition.Y = safeBounds.Height - personTexture.Height;
}
/// <summary>
/// Load your graphics content.
/// </summary>
protected override void LoadContent()
{
blockTexture = Content.Load<Texture2D>("Block");
personTexture = Content.Load<Texture2D>("Person");
font = Content.Load<SpriteFont>("Font");
player = new Player(personTexture, Vector2.Zero, 6.0f, new Rectangle(0, 0,
this.graphics.PreferredBackBufferWidth,
this.graphics.PreferredBackBufferHeight));
// Extract collision data
blockTextureData =
new Color[blockTexture.Width * blockTexture.Height];
blockTexture.GetData(blockTextureData);
personTextureData =
new Color[personTexture.Width * personTexture.Height];
personTexture.GetData(personTextureData);
// Create a sprite batch to draw those textures
spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
}
void HandleInput(KeyboardState keyState)
{
player.Input(keyState);
if (prevKB.IsKeyUp(Keys.F) && keyState.IsKeyDown(Keys.F))
{
this.graphics.ToggleFullScreen();
this.graphics.ApplyChanges();
}
}
protected override void Update(GameTime gameTime)
{
// Get input
KeyboardState keyboard = Keyboard.GetState();
GamePadState gamePad = GamePad.GetState(PlayerIndex.One);
HandleInput(Keyboard.GetState());
player.Update(gameTime);
prevKB = Keyboard.GetState();
// Allows the game to exit
if (gamePad.Buttons.Back == ButtonState.Pressed ||
keyboard.IsKeyDown(Keys.Escape))
{
this.Exit();
}
// Spawn new falling blocks
if (random.NextDouble() < BlockSpawnProbability)
{
float x = (float)random.NextDouble() *
(Window.ClientBounds.Width - blockTexture.Width);
blockPositions.Add(new Vector2(x, -blockTexture.Height));
}
// Get the bounding rectangle of the person
Rectangle personRectangle =
new Rectangle((int)personPosition.X, (int)personPosition.Y,
personTexture.Width, personTexture.Height);
// Update each block
personHit = false;
for (int i = 0; i < blockPositions.Count; i++)
{
// Animate this block falling
blockPositions[i] =
new Vector2(blockPositions[i].X,
blockPositions[i].Y + BlockFallSpeed);
// Get the bounding rectangle of this block
Rectangle blockRectangle =
new Rectangle((int)blockPositions[i].X, (int)blockPositions[i].Y,
blockTexture.Width, blockTexture.Height);
// Check collision with person
if (IntersectPixels(personRectangle, personTextureData,
blockRectangle, blockTextureData))
{
personHit = true;
}
// Remove this block if it have fallen off the screen
if (blockPositions[i].Y > Window.ClientBounds.Height)
{
blockPositions.RemoveAt(i);
// When removing a block, the next block will have the same index
// as the current block. Decrement i to prevent skipping a block.
i--;
}
}
base.Update(gameTime);
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice device = graphics.GraphicsDevice;
// Change the background to red when the person was hit by a block
if (personHit)
{
device.Clear(Color.Red);
}
else
{
device.Clear(Color.CornflowerBlue);
}
spriteBatch.Begin();
player.Draw(spriteBatch);
// Draw blocks
foreach (Vector2 blockPosition in blockPositions)
spriteBatch.Draw(blockTexture, blockPosition, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
/// <summary>
/// Determines if there is overlap of the non-transparent pixels
/// between two sprites.
/// </summary>
/// <param name="rectangleA">Bounding rectangle of the first sprite</param>
/// <param name="dataA">Pixel data of the first sprite</param>
/// <param name="rectangleB">Bouding rectangle of the second sprite</param>
/// <param name="dataB">Pixel data of the second sprite</param>
/// <returns>True if non-transparent pixels overlap; false otherwise</returns>
static bool IntersectPixels(Rectangle rectangleA, Color[] dataA,
Rectangle rectangleB, Color[] dataB)
{
// Find the bounds of the rectangle intersection
int top = Math.Max(rectangleA.Top, rectangleB.Top);
int bottom = Math.Min(rectangleA.Bottom, rectangleB.Bottom);
int left = Math.Max(rectangleA.Left, rectangleB.Left);
int right = Math.Min(rectangleA.Right, rectangleB.Right);
// Check every point within the intersection bounds
for (int y = top; y < bottom; y++)
{
for (int x = left; x < right; x++)
{
// Get the color of both pixels at this point
Color colorA = dataA[(x - rectangleA.Left) +
(y - rectangleA.Top) * rectangleA.Width];
Color colorB = dataB[(x - rectangleB.Left) +
(y - rectangleB.Top) * rectangleB.Width];
// If both pixels are not completely transparent,
if (colorA.A != 0 && colorB.A != 0)
{
// then an intersection has been found
return true;
}
}
}
// No intersection found
return false;
}
}
}
Provided the background of the texture is transparent, you could use Per-Pixel Collision detection.
Essentially it checks the pixels rather than a rectangular box to determine if a collision has occurred. Given your "player" is a ball, it's probably a good idea to use this anyway.
Considering that your map is only black & White, you could process it before starting the game and obtain coords of every Rectangle of black areas, and then use it to use simple collision detection by just checking intersection between the ball bounding box and rectangles.
For example, in pseudocode:
You have a list:
List<Rectangle> rects = new List<Rectangle>();
void LoadMap()
{
Texture2D map = Content.Load<Texture2D>(#"TexturePath");
//Get a 1D array with image's pixels
Color[] map_pixels = new Color[map.Width * map.Height];
map.GetData(map_pixels);
//Convert it in a 2D array
Color[,] map_pixels_2D = new Color[map.Width, map.Height];
for (int x = 0; x < map.Width; x++)
for (int y = 0; y < map.Height; y++)
map_pixels_2D[x, y] = map_pixels[x + y * map.Width];
//**NOTE THAT**: From here it is just an example, probably not working good,
//I wrote it just to share the idea
//Here goes the code to trace rectangles in the map
Rectangle r = Rectangle.Empty;
bool NWvertex_done = false, NEvertex_done = false, SWvertex_done = false;
for (int x = 0; x < map.Width; x++)
{
if (!SWvertex_done)
{
if (map_pixels_2D[x, y+1] == Color.White); //last bottom vertex
{
r.Height = r.Y + y;
SWvertex_done = true;
rects.Add(r);
NWvertex_done = false;
NEvertex_done = false;
r = Rectangle.Empty;
}
}
for (int y = 0; y < map.Height; y++)
{
if (map_pixels_2D[x, y] != Color.White
{
if (!NWvertex_done)
{
SWvertex_done = false;
r.X = x;
r.Y = y;
NWvertex_done = true;
}
else if(!NEvertex_done)
{
if (map_pixels_2D[x, y+1] == Color.White); //last right vertex
{
r.Width = r.X + x;
NEvertex_done = true;
}
}
}
}
}
}
public override void Update(GameTime gametime)
{
//maybe other things
//
foreach (Rectangle rect in rects)
{
//Better with Distance of ball-center and rect
if (ballRect.Intersect(rect))
{
//there is a collision!
//Do something
}
break;
}
//maybe other things
//
}