Very new developer here.
In my program I have a randomly generating world map using a simplex noise library. On top of this I am attempting to draw a tilemap of transparent 4x4 tiles that appear slightly translucent when the mouse is hovering over one.
I've got this working but it takes about 3 whole seconds for the highlighted tile to update to the mouse's current position. Is there anything I could do to solve this?
This is my code for the MouseState check in the tile class:
public override void Update(GameTime gameTime)
{
_previousMouse = _currentMouse;
_currentMouse = Mouse.GetState();
var mouseRectangle = new Rectangle(_currentMouse.X, _currentMouse.Y, 1, 1);
_isHovering = false;
if (mouseRectangle.Intersects(Rectangle))
{
_isHovering = true;
if (_currentMouse.LeftButton == ButtonState.Released && _previousMouse.LeftButton == ButtonState.Pressed)
{
Click?.Invoke(this, new EventArgs());
}
}
}
Sorry if this is formatted wrong or badly asked, first post so still getting to grips with everything :)
Invert the logic:
Instead of checking thousands of tile objects against the mouse, apply the mouse to a single object.
Assuming you have a list or array of tile objects:
Add a new object to check for mouse hover and click:
public class MouseDetect(Tile[] tiles) // replace with List<> as needed
{
int PrevHover = -1; // used to unHover
// if (Area == Screen) make the next two lines `const`, so the compiler will remove all uses...
int AreaX = 0; //Area x offset
int AreaY = 0; //Area y offset
int AreaW = 800; //Area width
int AreaH = 480; //Area height
const int Grid = 4; // assumes square
const int GridW = AreaW / Grid;
// I Will assume the `Delegate Click` in `Tile` is public
public void Update(MouseState ms, MouseState oms), //_currentMouse = ms and _previousMouse = oms;
{
int mouseIndex = (ms.X - AreaX) % Gridw + (ms.Y - AreaY) / GridW;
tiles[PrevHover].Hover = false;
PrevHover = mouseIndex;
tiles[PrevHover].Hover = true;
//Check Release
if(tiles[PrevHover].Hover && ms.LeftButton == ms.ButtonState.Released && oms.LeftButton == ButtonState.Pressed)
tiles[PrevHover].Click(tiles[PrevHover], new EventArgs());
}
}
Remove the Update from the Tile class.
Notes for anyone reading this later:
Never call Mouse.GetState(); more than once per step.
Predefined or framework names such as Rectangle should never be used as an identifier.
i.e renamed and corrected to CollRectangle
if (CollRectangle.Contains(ms.Position))
Related
I have a code for a crafting system that checks if the inventory has the ingredients needed to craft an item and adds a button to craft it. The problem is when I want to position my button it goes way off the canvas. I have seen some people saying that it has something to do with rect transform. I've been stuck with it for over an hour. Any help is appreciated.
I have tried
removing the setparent() function,
using anchoredPosition,
using localPosition
My code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Crafting : MonoBehaviour
{
public List<recipe> recipes = new List<recipe>();
public GameObject base_item, parent;
List<GameObject> items = new List<GameObject>();
public int y = 75;
public int x = -45;
public Inv inv;
private void Start()
{
inv = GetComponent<Inv>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Tab))
{
checkitems();
Debug.Log("y = " + y + " x = " + (x - 40));
}
}
public void checkitems()
{
for (int i = 0; i < recipes.Count; i++)
{
recipe r = recipes[i];
for (int x = 0; x < r.ingredients.Count; x++)
{
if (!inv.hasitem(r.ingredients[x])){
return;
}
}
showitem(r.result);
}
}
public void onClick(int _slot)
{
recipe r = recipes[_slot];
for (int i = 0; i < r.ingredients.Count; i++)
{
inv.removeitem(inv.getitem(r.ingredients[i]));
}
inv.additem(inv.getFirstAvailable(), r.result, r.stack);
}
public void showitem(string name)
{
GameObject obj = Instantiate(base_item);
if (items.Count != 0)
{
if (((items.Count) % 3) != 0)
{
Debug.Log("first thing");
obj.GetComponent<RectTransform>().position = new Vector2(x, y);
obj.transform.SetParent(parent.transform);
obj.SetActive(true);
items.Add(obj);
x = x + 40;
Debug.Log("x + 40");
}
else if (((items.Count + 1) % 3) == 0)
{
Debug.Log("second thing");
x = -45;
Debug.Log("x + 40");
y = y + 40;
Debug.Log(" y + 40");
obj.GetComponent<RectTransform>().position = new Vector2(x, y);
obj.transform.SetParent(parent.transform);
obj.SetActive(true);
items.Add(obj);
}
}else
{
obj.GetComponent<RectTransform>().position = new Vector2(x, y);
obj.transform.SetParent(parent.transform);
obj.SetActive(true);
items.Add(obj);
x = x + 40;
Debug.Log("x + 40");
}
}
}
Blue circle where it spawns. Red circle where I want it to be
Seems you are confusing a bunch of terms for being the issue of your problem. Firstly I want to address the red X over your scroll bar. Whenever this occurs, it means that your RectTransform of this UI object has been dragged from its positive vertices to negative or vice versa, causing it to almost invert. I would correct this but it is not the reason your objects are not childing correctly.
Generally, with UI objects, I would never use LocalPosition, just AnchoredPosition. LocalPosition is a field from Transform which I believe RectTransform inherits from. As RectTransforms have a lot of modifications to their position from pivots, anchors, and anchored positions, the LocalPosition will most likely need to recalculate data to properly move the object, whereas AnchoredPosition has already done these calculations.
I believe the issue with your current code is how you are using SetParent. There is a second parameter of SetParent which governs whether the object keeps the same position based in world space after being childed. As you are not passing in a new bool for this parameter, it is defaulting to true. As you want your objects to be childed to the parent but not keep their world space positions, you would want to pass in false.
In your case, as it looks as if you want to set objects in a grid-like pattern childed to this ScrollRect, I would attach a GridLayoutGroup to the Content of your scroll and child the new objects to this object. You can set the max columns of this grid and spacing to give the same layout you are attempting to achieve in code.
To summarize, I would remove all the hand placement you are doing in code with LocalPosition and AnchorPosition and just attach a GridLayoutGroup. To fix the current positioning of your objects relative to the parent, change all lines of obj.transform.SetParent(parent.transform); to obj.transform.SetParent(parent.transform, false);. If you want to keep changing position locally in code instead of a layout element, use SetParent first, and use AnchoredPosition instead of LocalPosition as the SetParent with false passed in will override the position you set.
I'm new to Monogame and C#. I'm attempting to render a test Tiled map on the screen using TiledSharp. However, when I try to draw multiple layers, they're clearly looping and drawing over one another.
Here's my draw method code:
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
spriteBatch.Begin(sortMode: SpriteSortMode.FrontToBack, blendState: BlendState.AlphaBlend, samplerState: SamplerState.PointClamp, null, null, null,
Matrix.CreateScale(6.0f));
for (currentTileLayer=0; currentTileLayer < easyMap.TileLayers.Count; currentTileLayer++)
{
Console.WriteLine(easyMap.TileLayers[currentTileLayer].Name.ToString());
for (var i = 0; i < easyMap.TileLayers[currentTileLayer].Tiles.Count; i++)
{
int gid = easyMap.TileLayers[currentTileLayer].Tiles[i].Gid;
// empty tile, do nothing
// gid => global id of tile
if (gid == 0)
{
}
else
{
int tileFrame = gid - 1;
int column = tileFrame % tilesetTilesWide;
int row = (int)Math.Floor((double)tileFrame / (double)tilesetTilesWide);
float x = (i % easyMap.Width) * easyMap.TileWidth;
float y = (float)Math.Floor(i / (double)easyMap.Width) * easyMap.TileHeight;
Rectangle tileSetRec = new Rectangle(tileWidth * column, tileHeight * row, tileWidth, tileHeight);
spriteBatch.Draw(easyTileset, new Rectangle((int)x, (int)y, tileWidth, tileHeight), tileSetRec, Color.White);
}
}
}
spriteBatch.End();
base.Draw(gameTime);
}
My original map looks like this:
When I run the code it looks like this:
There are three layers to the map. How can I fix my loops? Thanks!
You should use sorted layers since Tiled supports them very well. Using layers gives you much more possibilies in terms of map design. I also enables to make you character hide behind objects and so on.
In tiled, create your background layer first if needed several layers on top to avoid a wrong drawing order.
A simple solution could look like this
const int LAYER_BACKGROUNDLAYER = 0;
const int LAYER_FRONTLAYER = 1;
const int LAYER_TOPLAYER = 2;
Depending on how many layers you want to use.
Then you simply pass the layer number you want to draw to your Draw-Method. Usually from 0 to the max number.
public void DrawLayer(int layer)
{
for (var j = 0; j < curMap.Layers[layer].Tiles.Count; j++)
{
int gid = curMap.Layers[layer].Tiles[j].Gid;
if (gid == 0)
{
//empty tile
}
else
{
//draw your tile
}
}
}
In this case "curMap" is you current .tmx file loaded via curMap = new TmxMap(mapSource);
The draw code above should fit to your code since I've based it on the TiledSharp example as well ;)
I might be wrong but I think you need to sort your tiles from back to front. Whichever tiles should be on bottom should be rendered first.
in for (currentTileLayer=0; currentTileLayer < easyMap.TileLayers.Count; currentTileLayer++)
replace your TileLayers with var orderedTiles = easyMap.TileLayers.OrderBy(t => t.Tile.Gid)
I would like to say sorry in advance for the amount of code I will post, but I can't seem to get my collision detection to work, the player and the objects pass through each other with no effect when I play test.
I receive 0 warnings or errors, but the Player playerBot object does not seem to interact with any of the level items I have imported from GLEED2D.
One thing I did was to store the values of my item's rectangles and color arrays in lists so they could be all easily iterated through, maybe this is the source of the problem.
If you can spot why my code is not working I will be hugely grateful. I have removed any code that is definitely not relevant, and I am running VS 2010 with GLEED2D 1.3 if that helps.
Thanks again.
// Item Class ImageItem Downcast
public class ImageItem : Item
{
public Texture2D Texture;
}
// Level
Level level;
// Ints
int iNumOfItems = 0;
int iTextureDataListNum = 0;
int iRectangleListNum = 0;
// Lists
List<Color []> itemTextureDataList = new List<Color[]>();
List<Rectangle> itemRectangleList = new List<Rectangle>();
protected override void Initialize()
{
if (filename.Length > 0) level = Level.FromFile(filename, Content);
else level = Level.FromFile("level1.xml", Content);
foreach (Layer layer in level.Layers)
{
foreach (Item item in layer.Items)
{
iNumOfItems =+ 1;
}
}
// Creates Player Ship
playerBot = new Player(new Vector2(400f, 240f), new Vector2(0f, 0f));
base.Initialize();
}
protected override void LoadContent()
{
Texture2D pixel = new Texture2D(GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
pixel.SetData(new[] { Color.White });
spriteBatch = new SpriteBatch(GraphicsDevice);
// Player Bot
playerBot.LoadContent(Content, "Images/Player Bot Sprite Sheet", 40, 40, 4);
// Assigns level textures color data to array
foreach (Layer layer in level.Layers)
{
foreach (Item item in layer.Items)
{
ImageItem imageItem = item as ImageItem;
if (imageItem != null)
{
Texture2D texture = imageItem.Texture;
itemTextureDataList[iTextureDataListNum] = new Color[imageItem.Texture.Width * imageItem.Texture.Height];
imageItem.Texture.GetData(itemTextureDataList[iTextureDataListNum]);
iTextureDataListNum++;
}
}
}
// Creates a rectangle for every level texture
foreach (Layer layer in level.Layers)
{
foreach (Item item in layer.Items)
{
ImageItem imageItem = item as ImageItem;
if (imageItem != null)
{
itemRectangleList[iRectangleListNum] = new Rectangle((int)imageItem.Position.X, (int)imageItem.Position.Y, imageItem.Texture.Width, imageItem.Texture.Height);
iRectangleListNum++;
}
}
}
spriteBatch = new SpriteBatch(GraphicsDevice);
}
protected override void Update(GameTime gameTime)
{
// Player Update
playerBot.Update(gameTime);
((Sprite)playerBot).Update(gameTime);
// Check for player collisons with level
for (int i = 0; i < iNumOfItems - 1; i++)
{
if (IntersectPixels(playerBot.colRectangle, playerBot.textureDataArray, itemRectangleList[i], itemTextureDataList[i]) == true)
{
playerBot.StopMovement();
}
}
base.Update(gameTime);
}
// Level Collision Detection Method
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 fond
return false;
}
// Sprite Class
public void Update(GameTime gameTime)
{
textureDataArray = new Color[texture.Width * texture.Height];
texture.GetData(textureDataArray);
// Player Class
public void StopMovement()
{
velocity.X *= -1;
velocity.Y *= -1;
}
The first thing I'll say here is, you should aim to be more Object Oriented in your approach. Storing a list of textures and a list of rectangles alongside your list of Items is "bad" technique and going to eventually cause you some massive headaches when it comes to debugging.
So first of all, instead of having a list of Color[] and a list of Rectangle, add one Color[] and one Rectangle to your ImageItem class and work with those instead, or at least create a little class called "CollisionData" that has a Rectangle and a Color[] and store those in a single list.
Secondly, note that there is a Rectangle.Intersect(Rectangle A, Rectangle B) that gets your the rectangle of intersection. So you can tidy up your code a bit by using that.
Your color checking can be simplified to (ColorA.A * ColorB.A != 0) as either being zero will
cause the result to be zero.
Regarding not getting any errors, put a breakpoint at the start of the collision checking loop. Does the application break? If yes, what is the value of iNumItems? (you can hover over it to see the current value at point of breaking). If no, then that section of code isn't being reached. Put another breakpoint a bit further back and a bit further back until it gets hit, then figure out why the code isn't executing.
I am working on a space shooter game using XNA and have followed multiple tutorials to create a parallax background. So far I can get it to go along one axis, either X or Y, but not both at the same time. I have a camera class that follows the player, which the player moves (instead of the 'world'), since I figured it would be easier to just move the player versus moving everything else around the player.
So far, the background doesn't keep up with the player, and it also can't comprehend both axis at the same time. I thought about a tile engine, but that wouldn't let me parallax different layers would it?
Could anyone help me understand what I need to do, or recommend a tutorial that can do both axis at the same time? I can't seem to find the answer on my own this time.
Here is the code for my background class:
class Background
{
// Textures to hold the two background images
Texture2D spaceBackground, starsParallax;
int backgroundWidth = 2048;
int backgroundHeight = 2048;
int parallaxWidth = 2048;
int parallaxHeight = 2048;
int backgroundWidthOffset;
int backgroundHeightOffset;
int parallaxWidthOffset;
int parallaxHeightOffset;
public int BackgroundWidthOffset
{
get { return backgroundWidthOffset; }
set
{
backgroundWidthOffset = value;
if (backgroundWidthOffset < 0)
{
backgroundWidthOffset += backgroundWidth;
}
if (backgroundWidthOffset > backgroundWidth)
{
backgroundWidthOffset -= backgroundWidth;
}
}
}
public int BackgroundHeightOffset
{
get { return backgroundHeightOffset; }
set
{
backgroundHeightOffset = value;
if (backgroundHeightOffset < 0)
{
backgroundHeightOffset += backgroundHeight;
}
if (backgroundHeightOffset > backgroundHeight)
{
backgroundHeightOffset -= backgroundHeight;
}
}
}
public int ParallaxWidthOffset
{
get { return parallaxWidthOffset; }
set
{
parallaxWidthOffset = value;
if (parallaxWidthOffset < 0)
{
parallaxWidthOffset += parallaxWidth;
}
if (parallaxWidthOffset > parallaxWidth)
{
parallaxWidthOffset -= parallaxWidth;
}
}
}
public int ParallaxHeightOffset
{
get { return parallaxHeightOffset; }
set
{
parallaxHeightOffset = value;
if (parallaxHeightOffset < 0)
{
parallaxHeightOffset += parallaxHeight;
}
if (parallaxHeightOffset > parallaxHeight)
{
parallaxHeightOffset -= parallaxHeight;
}
}
}
// Constructor when passed a Content Manager and two strings
public Background(ContentManager content,
string sBackground, string sParallax)
{
spaceBackground = content.Load<Texture2D>(sBackground);
backgroundWidth = spaceBackground.Width;
backgroundHeight = spaceBackground.Height;
starsParallax = content.Load<Texture2D>(sParallax);
parallaxWidth = starsParallax.Width;
parallaxHeight = starsParallax.Height;
}
public void Draw(SpriteBatch spriteBatch)
{
// Draw the background panel, offset by the player's location
spriteBatch.Draw(
spaceBackground,
new Rectangle(-1 * backgroundWidthOffset,
-1 * backgroundHeightOffset,
backgroundWidth,
backgroundHeight),
Color.White);
// If the right edge of the background panel will end
// within the bounds of the display, draw a second copy
// of the background at that location.
if (backgroundWidthOffset > backgroundWidth)
{
spriteBatch.Draw(
spaceBackground,
new Rectangle(
(-1 * backgroundWidthOffset) + backgroundWidth, 0,
backgroundWidth, backgroundHeight),
Color.White);
}
else //(backgroundHeightOffset > backgroundHeight - viewportHeight)
{
spriteBatch.Draw(
spaceBackground,
new Rectangle(
0, (-1 * backgroundHeightOffset) + backgroundHeight,
backgroundHeight, backgroundHeight),
Color.White);
}
// Draw the parallax star field
spriteBatch.Draw(
starsParallax,
new Rectangle(-1 * parallaxWidthOffset,
0, parallaxWidth,
parallaxHeight),
Color.SlateGray);
// if the player is past the point where the star
// field will end on the active screen we need
// to draw a second copy of it to cover the
// remaining screen area.
if (parallaxWidthOffset > parallaxWidth)
{
spriteBatch.Draw(
starsParallax,
new Rectangle(
(-1 * parallaxWidthOffset) + parallaxWidth,
0,
parallaxWidth,
parallaxHeight),
Color.White);
}
}
}
The main game then has ties to move the backgrounds, along with the player.
background.BackgroundWidthOffset -= 2;
background.ParallaxWidthOffset -= 1;
Visually, the background is sort of jumpy, and seems to randomly skip or overlap background tiles.
I've used this method in the past with great results:
http://www.david-gouveia.com/scrolling-textures-with-zoom-and-rotation/
It uses a shader to accomplish the effect, resulting in a fast implementation.
There is a complete example here.
I am brand new to XNA and C# (I started 2 days ago, so please forgive the sloppy programming), and I have run into a problem with a simple game I am making. I can't seem to find the solution in any of the online tutorials. I am trying to utilize rectangle collision detection but I cannot get it to work properly. The character falls completely through the floor and it seems there is no collision ever registering in any place. I cannot find my logic error here. Below I posted a bit of my code that pertains to the collision detection. I can post more if necessary. Thank you for the help in advance!
Level.cs
//The method below is called in the initial LoadContent method in Game1.cs. 'LoadBlocks()' is supposed to read in a text file and setup the level's "blocks" (the floor or platforms in the game).
public void LoadBlocks(int level){
if (level == 0)
{
fileName = "filepath";
}
System.IO.StreamReader file = new System.IO.StreamReader(fileName);
while ((line = file.ReadLine()) != null)
{
for (int i = 0; i < 35; i++)
{
rectBlock = new Rectangle((int)positionBlock.X, (int)positionBlock.Y, width / 30, height / 30);
if (line.Substring(i, 1).Equals(","))
{
positionBlock.X += (width / 100) * 24;
}
if (line.Substring(i, 1).Equals("#"))
{ //block -> (bool isPassable, Texture2D texture, Rectangle rectangle, Vector2 position)
block = new Block(false, textureBlock, rectBlock, positionBlock);
blocks.Add(block);
positionBlock.X += (width / 100) * 24;
}
}
positionBlock.Y += (height / 100) * 8;
positionBlock.X = 0;
}
}
//This method below updates the character 'bob' position and velocity.
public void UpdatePlayer()
{
bob.position += bob.velocity;
bob.rectangle = new Rectangle((int)bob.position.X, (int)bob.position.Y, width / 30, height / 30);
float i = 1;
bob.velocity.Y += 0.15f * i;
foreach (Block block in blocks)
{
if (bob.isOnTopOf(bob.rectangle, block.rectangle))
{
bob.velocity.Y = 0f;
bob.hasJumped = false;
}
}
}
Character.cs
//Here is my whole Character class for 'bob'
public class Character
{
int height = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
int width = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
int health;
String name;
bool gender;
Texture2D texture;
public Vector2 position;
public Vector2 velocity;
public bool hasJumped;
public Rectangle rectangle;
public Character(int newHealth, String newName, bool newGender, Texture2D newTexture, Vector2 newPosition)
{
health = newHealth;
gender = newGender;
name = newName;
texture = newTexture;
position = newPosition;
hasJumped = true;
rectangle = new Rectangle(0,0, width/30,height/30);
velocity = Vector2.Zero;
}
public bool isOnTopOf(Rectangle r1, Rectangle r2)
{
const int penetrationMargin = 5;
return (r1.Bottom >= r2.Top - penetrationMargin &&
r1.Bottom <= r2.Top &&
r1.Right >= r2.Left + 5 &&
r1.Left <= r2.Right - 5);
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture, rectangle,null, Color.White,0,position,SpriteEffects.None,0);
}
}
Sorry if this is too much posted code or is confusing, but any help is greatly appreciated! Thanks!
Your test condition for intersection appears to be incorrect, at least. There might be other problems with the code (there are several just giving a quick look at it), but let's focus on this:
r1.Bottom >= r2.Top - penetrationMargin &&
r1.Bottom <= r2.Top &&
r1.Right >= r2.Left + 5 &&
r1.Left <= r2.Right - 5;
First, don't code what exists in the library for you. Xna Rectangles have an Intersects method, so scrap that code and use it instead.
We'll look at the condition anyway:
r1's Bottom is below r2's Top minus a margin AND
r1's Bottom is above r2's Top AND (...)
This is already impossible. r1.Bottom cannot be both below and above r2.Top.
Further, even if all the comparisons were correct, you are using AND everywhere (&&), which means that the condition is only true if all 4 of them are true. Basically, you are not testing for intersection, you are testing for containement, i.e. you are testing that r1 is entirely within r2. An intersection test would join the conditions using OR (||) not AND (&&).
But again, although you might want to code it yourself for educative purposes, the best solution from an engineering perspective is to use what the library provides, here Rectangle.Intersects.
I figured it out, but I'm not sure why this actually works. Something was wrong with my draw method. I originally had:
spriteBatch.Draw(texture, rectangle, null, Color.White, 0, position, SpriteEffects.None, 0);
as my method but then I changed this to
spriteBatch.Draw(texture, rectangle, Color.White);
and now everything works fine.