Drawing different Textures with a single Basic Effect, XNA 4.0 - c#

I have a problem in the game I wrote with XNA. I recently added Textured polygons, and saw that every textured polygon shared the same texture although I changed it before calling. The code I am using:
if (countMeshes > 0)
{
for (int i = 0; i < countMeshes; i++)
{
TexturedMesh curMesh = listMeshes[i];
if (curMesh.tex == null)
{
drawEffect.Texture = WHITE_TEXTURE;
}
else
{
drawEffect.Texture = curMesh.tex;
}
drawEffect.Techniques[0].Passes[0].Apply();
graphics.DrawUserPrimitives(PrimitiveType.TriangleList, curMesh.verts, 0, curMesh.count);
}
}
Now, the first that came into my mind would be to create a BasicEffect for each Texture I need to draw. But I think that would be a bit of a overkill so my question is: How should I do it?
PS: I double checked everything, the UV Coords are fine, and it is 2D.

It seems to be the only way, the way I did it was to create a Dictionary with the texture as the key and a struct of a basic effect and a list of Vertexs as the value.
Something like this:
public struct MeshEffect
{
public TexturedMesh mesh;
public BasicEffect effect;
}
and the Dictionary:
private Dictionary<Texture2D, MeshEffect> texturedMeshes = new Dictionary<Texture2D, MeshEffect>();
But it all really depends on how you handle drawing, but one thing is sure, you can't use more than one texture on one BasicEffect.

Related

grab grid tiles in unity

First of all: I'm quite new to unity and c#, so please be nice if I ask dumb questions.
I'm trying to make a game like minesweeper. So I want an easy grid with covered tiles and if you klick on one, it opens up.
I use a main script I create a grid like so:
private void CreateTieleSet(int width, int height){
_tiles = new Dictionary<Vector2, Tile>();
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
var spawnedTile = Instantiate(_tilePrefab, new Vector3(x, y), Quaternion.identity);
spawnedTile.name = $"Tile {x} {y}";
spawnedTile.status = field[x, y];
_tiles[new Vector2(x, y)] = spawnedTile;
}
}
}
I use the function
public Tile GetTileAtPosition(Vector2 pos)
{
if(_tiles.TryGetValue(pos, out var tile))
{
return tile;
}
return null;
}
to get the tile at the position xy, but I can't use it since
Tile tempTile = GetTileAtPosition(new Vector2(x, y));
tempTile.ChangeSprite(field[x, y]); //example-funktion from tile-script
allways results in NullReferenceExeption-Error. I know the probleme, since I allways struggle with using scripts from other tiles. Bus usually I can use [SerializeField] Tile... and than dragdrop it onto it. In this case however I obvioulsy can't do that.
Btw: I realy tried to solve this problem with other solutions found online, but everyone has complete different ideas how to do it and nothing seems to work for me :/
Your best bet for this one is going to be using Unity's built-in Tilemaps - you'll want to take your sprites for Minesweeper and add them to a TilePalette, and paint them onto a GameObject with a Grid component, with children GameObjects holding a Tilemap component - one per layer. Then, when you want to change the sprite, you can do something like the following:
using UnityEngine;
using UnityEngine.Tilemaps;
class GridManager
{
[SerializeField] Tilemap gridDisplayLayer;
public void ChangeSpriteAtLocation(Vector3Int cellLocation, Tile tileToChangeTo)
{
gridDisplayLayer.SetTile(cellPosition, tile);
}
}
If you want the simplest answer, just add Tile members to the class above for swapping between them and call ChangeSpriteAtLocation during runtime. Might need to change the flags on the Tilemap to change the sprite and such, but I think that should be good enough to get you started :) When you generate the TilePalette, Unity will give you Tile assets to save that you can then reference for the above example method.
I'm still getting all the intricacies of Tilemaps myself, though, and I'm definitely more design minded, so I won't be surprised if someone has a more succinct or efficient solution.
Hope this helps get you started, though!
Vellv

How to make a list of gameObjects disappear animated? (or how to fix weird behaviour when setting alpha value)

I am trying to make a list of gameObjects disappear when the player enters a room, the best way I could think about doing it, was changing the material alpha frame by frame. If there is an alternative to this method that is more performatic, please tell! (not that I noticed any bad performance as this seems simple enough)
I expect the player to enter a room and all of the gameObjects in that list to disappear at the same time and at the same rate (as if they were all just one object having their transparency changed in runtime) but what happens is (I've made a video: https://youtu.be/z1pz2Te_hDg ) that some gameObjects change before others, some disappear way faster than expected and just in the end after the others, it all looks weird.
Changing the alpha of only one object at a time seems fine, but since I need to loop through all of the materials, I can't simply put all objects as a child of one gameObject.
I've tried to change the alpha without making a custom shader with an alpha property by accessing the default alpha in the default "HDPR/Lit" shader and it behaves the same.
This problem seems easy, I feel like I am doing something stupidly wrong but I've been at this for a couple of weeks.
Here is my "RoomTransparencyController" script:
bool transparent = false;
public float enhance = 5;
private struct ShaderPropertyIDs {
public int AlphaRef;
}
private ShaderPropertyIDs shaderProps;
public List<GameObject> gameObjectList;
public List<Material> materialList;
void Start() {
foreach(GameObject obj in gameObjectList) {
foreach(Material mat in obj.GetComponent<MeshRenderer>().materials) {
materialList.Add(mat);
}
}
// Cache property IDs
shaderProps = new ShaderPropertyIDs() {
AlphaRef = Shader.PropertyToID("AlphaRef"),
};
}
void Update() {
UpdateChildsAlpha3(transparent);
}
private void OnTriggerEnter(Collider col) {
if(col.tag == "Player") {
transparent = true;
}
}
private void OnTriggerExit(Collider col) {
if(col.tag == "Player") {
transparent = false;
}
}
void UpdateChildsAlpha3(bool transparent) {
foreach(Material mat in materialList) {
mat.SetFloat("AlphaRef", Mathf.Clamp(mat.GetFloat("AlphaRef") + Time.deltaTime * enhance * (transparent ? -1 : 1), 0, 1));
}
}
PS: This script should mainly contain props and specific walls from a room so there is a script in every room with its gameObject list for better organization. So I thought that each room having its script was the best way to organize and replicate.
If you want the objects to be gone, then use a for loop with Destroy() on all the objects, and if you want them to stop being visible, just use SetActive(). If you want them to work, but just stop being visible, you can disable the renderer component.
It will be much better if you try to use some tweens to solve this problem. Like doTween or LeanTween, where they have some better-optimized way to reduce alpha or anything. Try to play around with it.

Unity color wont assign to ball or what it should be painting

private Color solveColor;
void Start()
{
Color[] colors = { Color.cyan, Color.red, Color.green, new Color(245, 195, 29), Color.yellow, Color.magenta };
int lengthOfColors = colors.Length;
int solveColor = UnityEngine.Random.Range(0, lengthOfColors);
}
private void start()
{
GetComponent<MeshRenderer>().material.color = solveColor;
}
private void FixedUpdate()
{
// Set the balls speed when it should travel
if (isTraveling) {
rb.velocity = travelDirection * speed;
}
// Paint the ground
Collider[] hitColliders = Physics.OverlapSphere(transform.position - (Vector3.up/2), .05f);
int i = 0;
while (i < hitColliders.Length)
{
GroundPiece ground = hitColliders[i].transform.GetComponent<GroundPiece>();
if (ground && !ground.isColored)
{
ground.Colored(solveColor);
}
The above code is supposed to pick one color from the colors array and assign it to both the ball and balls painting ability (whenever the ball collides with the ground it changes its color) however the paint the ball leaves is always black and the ball itself is always orange (pretty sure the ball color is coming from its default). I can't figure out why this is happening any help is very appreciated.
Thank you for your time
In the code you provided, nowhere do you set the material color of the ball again aside from Start. If you want to have the particles behind the ball leave different colors, you will need to instantiate a new instance of the material. The reason for this is because materials in Unity are default shared between all instances of that particular material.
All of this info and a bit more can be found on the Material docs page.
As you have a fixed size of colors you are using, I would instead create 6 new materials and make an array of materials instead. Now, instead of randomly picking a color, pick a material and assign it to the ball or your new instanced painting ability. I am also confused as to why you are placing your array of colors inside of your Start function. It would be localized to that function only then. You also appear to have two Start functions, which is odd. One being the Monobehaviour Start and another start. Unless that is intended, your second start will not be run unless you call it.
Now to get to the solution I was talking about.
// assign these in the inspector to your new materials
[SerializeField] private List<Material> materials = new List<Material>();
private MeshRenderer meshRender;
private void Start()
{
meshRenderer = GetComponent<MeshRenderer>();
// set our first random Material
SetNewMaterialColor();
}
private void SetNewMaterialColor()
{
meshRenderer.material = GrabNewMaterial();
}
private void FixedUpdate()
{
// Set the balls speed when it should travel
if (isTraveling) {
rb.velocity = travelDirection * speed;
}
// Paint the ground
Collider[] hitColliders = Physics.OverlapSphere(transform.position - (Vector3.up/2), .05f);
int i = 0;
while (i < hitColliders.Length)
{
GroundPiece ground = hitColliders[i].transform.GetComponent<GroundPiece>();
if (ground && !ground.isColored)
{
// change this from a color to a material instead
ground.Colored(meshRenderer.material);
// set a new material to your main object
SetNewMaterialColor();
}
}
}
private Material GrabNewMaterial()
{
return materials[UnityEngine.Random.Range(0, materials.Count)];
}
You will need to change your Colored function to take in a Material instead of a Color. If you want the implementation to be more dynamic, you can instead create an instance of your material and set the color dynamically, but as you have a fixed size of colors I do not think you need to do that.
Edit: The one other option which involves creating a new shader would be to utilize [PerRendererData] meaning each object for a property field is rendered individually. I would go with the previous option as either option using shaders or instanced materials is a bit more complex.
You would need to use a MaterialPropertyBlock and can then assign the color when you want. It would look something like
public void SetNewColor()
{
// create a new material property block
MaterialPropertyBlock tmpBlock = new MaterialPropertyBlock();
// grab the current block from our renderer
meshRender.GetPropertyBlock(tmpBlock);
// set our changes to the block
tmpBlock.SetColor("_Color", YourColorHere);
// now apply our changes
tmpRend.SetPropertyBlock(tmpBlock);
}
And you would need to create a new shader that laters the Main Color property by using the PerRendererData attribute.
Properties
{
[PerRendererData]_Color("Main Color", Color) = (1,1,1,1)
...
Also, one other question I have is why you are using Physics.OverlapSphere instead of just an OnCollisionEnter? Or if your game is 2D, then OnCollisionEnter2D and let the physics engine handle how collisions work, then just change the colors when the collision occurs?
Edit: Here are the answer to your questions - let me know if you have more.
In the line "[SerializeField] private List materials = new
List();" which section do I need to replace with the
materials and how?
The line as is is fine. By using [SerializeField] it exposes this list to the editor. You will want to create several new duplicate materials that use your 6 different colors. Instead of setting the colors, you will be setting materials now. What I mean by inspector and editor is you can find the object that has this script on it in Unity, select it (it must be a Prefab or in the scene), then a tab of the Unity editor will populate with information about this object. Find the script portion and find the field materials. There should be a drop-down arrow, click it and set the number to 6 (or however many material swaps you want). Now create 6 new materials with your colors and drag them into the boxes that appeared.
Would it be something like writing "./Materials/Ball 1" in the () for
example?
Nope! You would be assigning this data in the inspector, so the data would be stored in the list without referencing them in code.
And I'm not sure how to assign this to my ball using "[SerializeField]
private GameObject paintObject = null;"
Similarly, this would appear in the inspector. However, remove this line as I misunderstood your original question and accidentally left this in. I assumed that your paint object was a Prefab that you were spawning after the ball bounced, not the ground that you were changing the color of.
I get the error "Argument 1: cannot convert from
'UnityEngine.Material' to 'UnityEngine.Color'"
Yep! So as I mentioned in the comments, your function call to your paint object is most likely currently taking a Color parameter. As I changed your implementation to instead directly set Material, you will need to change how that function signature. Specifically the line:
ground.Colored(meshRenderer.material);
You have some object ground that is of type GroundPiece and has a function called Colored. I assume it currently look something like:
public void Colored(Color color){...}
You want to change this instead to:
public void Colored(Material mat{...}
After changing it, instead of changing the ground's color in this script, you would change its material directly. Let me know if you have more questions.

Extreme tearing when I move the camera quickly

I am creating a minecraft clone, and whenever I move the camera even a little bit fast there is a big tear between the chunks as shown here:
Each chunk is 32x32x32 cubes and has a single vertex buffer for each kind of cube, in case it matters. I am drawing 2D text on the screen as well, and I learned that I had to set the graphic device state for each kind of drawing. Here is how I'm drawing the cubes:
GraphicsDevice.Clear(Color.LightSkyBlue);
#region 3D
// Set the device
device.BlendState = BlendState.Opaque;
device.DepthStencilState = DepthStencilState.Default;
device.RasterizerState = RasterizerState.CullCounterClockwise;
// Go through each shader and draw the cubes of that style
lock (GeneratedChunks)
{
foreach (KeyValuePair<CubeType, BasicEffect> KVP in CubeType_Effect)
{
// Iterate through each technique in this effect
foreach (EffectPass pass in KVP.Value.CurrentTechnique.Passes)
{
// Go through each chunk in our chunk map, and pluck out the cubetype we care about
foreach (Vector3 ChunkKey in GeneratedChunks)
{
if (ChunkMap[ChunkKey].CubeType_TriangleCounts[KVP.Key] > 0)
{
pass.Apply(); // assign it to the video card
KVP.Value.View = camera.ViewMatrix;
KVP.Value.Projection = camera.ProjectionMatrix;
KVP.Value.World = worldMatrix;
device.SetVertexBuffer(ChunkMap[ChunkKey].CubeType_VertexBuffers[KVP.Key]);
device.DrawPrimitives(PrimitiveType.TriangleList, 0, ChunkMap[ChunkKey].CubeType_TriangleCounts[KVP.Key]);
}
}
}
}
}
#endregion
The world looks fine if I'm standing still. I thought this might be because I'm in windowed mode, but when I toggled full screen the problem persisted. I also assume that XNA is double buffered by itself? Or so google has told me.
I had a similar issue - I found that I had to call pass.Apply() after setting all of the Effect's parameters...
The fix so far has been to use 1 giant vertex buffer. I don't like it, but that's all that seems to work.

Help with C# program design implementation: multiple array of lists or a better way?

I'm creating a 2D tile-based RPG in XNA and am in the initial design phase. I was thinking of how I want my tile engine to work and came up with a rough sketch. Basically I want a grid of tiles, but at each tile location I want to be able to add more than one tile and have an offset. I'd like this so that I could do something like add individual trees on the world map to give more flair. Or set bottles on a bar in some town without having to draw a bunch of different bar tiles with varying bottles.
But maybe my reach is greater than my grasp. I went to implement the idea and had something like this in my Map object:
List<Tile>[,] Grid;
But then I thought about it. Let's say I had a world map of 200x200, which would actually be pretty small as far as RPGs go. That would amount to 40,000 Lists. To my mind I think there has to be a better way. Now this IS pre-mature optimization. I don't know if the way I happen to design my maps and game will be able to handle this, but it seems needlessly inefficient and something that could creep up if my game gets more complex.
One idea I have is to make the offset and the multiple tiles optional so that I'm only paying for them when needed. But I'm not sure how I'd do this. A multiple array of objects?
object[,] Grid;
So here's my criteria:
A 2D grid of tile locations
Each tile location has a minimum of 1 tile, but can optionally have more
Each extra tile can optionally have an x and y offset for pinpoint placement
Can anyone help with some ideas for implementing such a design (don't need it done for me, just ideas) while keeping memory usage to a minimum?
If you need more background here's roughly what my Map and Tile objects amount to:
public struct Map
{
public Texture2D Texture;
public List<Rectangle> Sources; //Source Rectangles for where in Texture to get the sprite
public List<Tile>[,] Grid;
}
public struct Tile
{
public int Index; //Where in Sources to find the source Rectangle
public int X, Y; //Optional offsets
}
What you could do is simply have an array of Tile:
class Grid
{
Tile[,] grid;
}
... and have that Tile class have a List<Sprite> in it:
class Tile
{
List<Sprite> sprites;
}
... and that Sprite class would have your texture and offset:
class Sprite
{
Vector2 offset;
Texture2D texture;
}
Finalize all that by having draw methods:
class Grid
{
Tile[,] grid;
void Draw(GraphicsDevice graphics)
{
// call your tiles Draw()
}
}
class Tile
{
List<Sprite> sprites;
void Draw(GraphicsDevice graphics, int x, int y)
{
// call your sprites Draw()
}
}
class Sprite
{
Vector2 offset;
Texture2D texture; // or texture and rectangle, or whatever
void Draw(GraphicsDevice graphics, int x, int y)
{
// draw the sprite to graphics using x, y, offset and texture
}
}
Of course it gets much more complicated than that but you should get the idea.
Separating all your concerns in different classes let you easily add new fonctionnality that won't conflict with existing code. Trying to mash all your data in a single object such as your List<Tile>[,] is bad form and will eventually bite you when you try to expand.
It seems like your approach is confusing presentation with behavior. If the behavior of the game is tile based then design for that functionally and then code up a presentation as the result of the state of the board.
You essentially want a sparse matrix to represent "decorations" on each tile. A sparse matrix is a matrix structure where not all elements need have a value. There are C# libraries out there that represent them.
A simple approach would be to use an ordinary dictionary where the key is a Tile # (unique number of each tile) that could for example be calculated using the same type of formula used to address video memory: Y * MAXIMUM_X + X. For a given tile, just check if there's an entry for it's unique Tile #. The dictionary should probably contain a list of decorations for that particular tile:
Dictionary<int, List<Sprites>> spritesPerTile;
// ...
if (spritesPerTile.ContainsKey(tileNumber))
{
List<Sprites> decorationsThisTile = spritesPerTile[tileNumber];
// Proceed to render sprites on this tile.
}

Categories