Sign in to follow this  
SpikeViper

Unity Adding Subtypes Of A Class [Optimizing Blocks]

Recommended Posts

I have come far in my quest to optimize. After adding chunk pools, rewriting generation, and shrinking the data in my blocks, I've hit a roadblock. For my blocks to be changed (they are instances of a class), they need to be destroyed and a new instance of another block class takes its place. I want to know the best way to add subtypes to my block class, so instead of [new Block()] I can use [Block.BlockType =], fixing my garbage collection issues. I want my blocks to keep their functionality, including the ability to have their own methods in the future.

 

Current Block class:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[System.Serializable]

public class Block
{
    public int temperature;
    public bool light;
    public float LightRange;
    public float LightIntensity;
    public int density;
    public string BlockName;
    public byte LR;
    public byte LG;
    public byte LB;
    public byte LA;

    public Block()
    {
        this.light = false;
        this.BlockName = "Undefined";
    }

    public enum Direction { north, east, south, west, up, down };

    public struct Tile { public int x; public int y;}

    const float tileSize = 0.0625f;


    public virtual MeshData Blockdata
     (PlanetChunk planetchunk, int x, int y, int z, MeshData meshData)
    {

            meshData.useRenderDataForCol = true;

            if (!planetchunk.GetBlock(x, y + 1, z).IsSolid(Direction.down))
            {
                meshData = FaceDataUp(planetchunk, x, y, z, meshData);
            }

            if (!planetchunk.GetBlock(x, y - 1, z).IsSolid(Direction.up))
            {
                meshData = FaceDataDown(planetchunk, x, y, z, meshData);
            }

            if (!planetchunk.GetBlock(x, y, z + 1).IsSolid(Direction.south))
            {
                meshData = FaceDataNorth(planetchunk, x, y, z, meshData);
            }

            if (!planetchunk.GetBlock(x, y, z - 1).IsSolid(Direction.north))
            {
                meshData = FaceDataSouth(planetchunk, x, y, z, meshData);
            }

            if (!planetchunk.GetBlock(x + 1, y, z).IsSolid(Direction.west))
            {
                meshData = FaceDataEast(planetchunk, x, y, z, meshData);
            }

            if (!planetchunk.GetBlock(x - 1, y, z).IsSolid(Direction.east))
            {
                meshData = FaceDataWest(planetchunk, x, y, z, meshData);
            }



            if (light == true)
            {

                if (planetchunk.GetBlock(x - 1, y, z).IsSolid(Direction.east) == false || planetchunk.GetBlock(x + 1, y, z).IsSolid(Direction.west) == false || planetchunk.GetBlock(x, y, z - 1).IsSolid(Direction.north) == false ||
                    planetchunk.GetBlock(x, y, z + 1).IsSolid(Direction.south) == false || planetchunk.GetBlock(x, y - 1, z).IsSolid(Direction.up) == false || planetchunk.GetBlock(x, y + 1, z).IsSolid(Direction.down) == false)
                {
                    meshData.AddLight(x, y, z, LR, LG, LB, LA, LightRange, LightIntensity);
                }

            }

            return meshData;


    }

    protected virtual MeshData FaceDataUp
        (PlanetChunk planetchunk, int x, int y, int z, MeshData meshData)
    {
        meshData.AddVertex(new Vector3(x - 0.5f, y + 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y + 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y + 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x - 0.5f, y + 0.5f, z - 0.5f));

        meshData.AddQuadTriangles();
        meshData.uv.AddRange(FaceUVs(Direction.up));
        return meshData;
    }

    protected virtual MeshData FaceDataDown
        (PlanetChunk planetchunk, int x, int y, int z, MeshData meshData)
    {
        meshData.AddVertex(new Vector3(x - 0.5f, y - 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y - 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y - 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x - 0.5f, y - 0.5f, z + 0.5f));

        meshData.AddQuadTriangles();
        meshData.uv.AddRange(FaceUVs(Direction.down));
        return meshData;
    }

    protected virtual MeshData FaceDataNorth
        (PlanetChunk planetchunk, int x, int y, int z, MeshData meshData)
    {
        meshData.AddVertex(new Vector3(x + 0.5f, y - 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y + 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x - 0.5f, y + 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x - 0.5f, y - 0.5f, z + 0.5f));

        meshData.AddQuadTriangles();
        meshData.uv.AddRange(FaceUVs(Direction.north));
        return meshData;
    }

    protected virtual MeshData FaceDataEast
        (PlanetChunk planetchunk, int x, int y, int z, MeshData meshData)
    {
        meshData.AddVertex(new Vector3(x + 0.5f, y - 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y + 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y + 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y - 0.5f, z + 0.5f));

        meshData.AddQuadTriangles();
        meshData.uv.AddRange(FaceUVs(Direction.east));
        return meshData;
    }

    protected virtual MeshData FaceDataSouth
        (PlanetChunk planetchunk, int x, int y, int z, MeshData meshData)
    {
        meshData.AddVertex(new Vector3(x - 0.5f, y - 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x - 0.5f, y + 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y + 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x + 0.5f, y - 0.5f, z - 0.5f));

        meshData.AddQuadTriangles();
        meshData.uv.AddRange(FaceUVs(Direction.south));
        return meshData;
    }

    protected virtual MeshData FaceDataWest
        (PlanetChunk planetchunk, int x, int y, int z, MeshData meshData)
    {
        meshData.AddVertex(new Vector3(x - 0.5f, y - 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x - 0.5f, y + 0.5f, z + 0.5f));
        meshData.AddVertex(new Vector3(x - 0.5f, y + 0.5f, z - 0.5f));
        meshData.AddVertex(new Vector3(x - 0.5f, y - 0.5f, z - 0.5f));
            

            meshData.AddQuadTriangles();
        meshData.uv.AddRange(FaceUVs(Direction.west));
        return meshData;
    }

    public virtual Tile TexturePosition(Direction direction)
    {
        Tile tile = new Tile();
        tile.x = 0;
        tile.y = 0;

        return tile;
    }

    public virtual Vector2[] FaceUVs(Direction direction)
    {
        Vector2[] UVs = new Vector2[4];
        Tile tilePos = TexturePosition(direction);

        UVs[0] = new Vector2(tileSize * tilePos.x + tileSize,
            tileSize * tilePos.y);
        UVs[1] = new Vector2(tileSize * tilePos.x + tileSize,
            tileSize * tilePos.y + tileSize);
        UVs[2] = new Vector2(tileSize * tilePos.x,
            tileSize * tilePos.y + tileSize);
        UVs[3] = new Vector2(tileSize * tilePos.x,
            tileSize * tilePos.y);

        return UVs;
    }

    public virtual bool IsSolid(Direction direction)
    {
        switch (direction)
        {
            case Direction.north:
                return true;
            case Direction.east:
                return true;
            case Direction.south:
                return true;
            case Direction.west:
                return true;
            case Direction.up:
                return true;
            case Direction.down:
                return true;
        }

        return false;
    }















}

Example of a Block type:

using UnityEngine;
using System.Collections;

[System.Serializable]

public class BlockStone : Block
{

    public BlockStone()
        : base()
    {

        this.temperature = 0;
        this.light = false;
        this.density = 400;
        this.BlockName = "Stone";

    }

    public override Tile TexturePosition(Direction direction)
    {
        Tile tile = new Tile();

        tile.x = 3;
        tile.y = 1;

        return tile;
    }

}

Thanks in advance!

Share this post


Link to post
Share on other sites

Also, unless you really need Blocks to have string names, I think I'd go with an enumerated type, that string is probably your biggest field in that class.

 

As an aside, does it matter how members are placed for alignment in a C# class?  My C++ sense wants me to rearrange everything to be largest member variable first, then smallest.

Edited by ferrous

Share this post


Link to post
Share on other sites

If all that is different between block types is a few field/parameter values you should not create subtypes, this will add complexity to your code that is just pointless. It is best to favor composition in place of inheritance where possible as it makes your design more fluid to change if you realise you have boxed yourself into a corner etc.

 

To get the same effect you simply need a static on your Block object call BuildStoneBlock() or similar that is as follows

 

public static Block BuildStoneBlock()

{

    return new Block

    {

        temperature = 0,
        light = false,
        density = 400,
        BlockName = "Stone"

    };

}

 

or even this if blocks can change type a lot

 

public static void MakeBlockIntoStone(Block block)

{

    block.temperature = 0;

    block.light = false;
    block.density = 400;
    block.BlockName = "Stone";

}
 

 

You could even argue that if all the difference is are some parameters and these do not change, so density of all stone blocks is 400 and say 100 for some other block type you could extract this data to a lookup table and have a field/property called something like blockType that has a string/enum which acts as the lookup key

 

This would in turn give you zero memory churn as you simply change the block type and it then gains the standard attributes of that type of block.

 

var density = blockAttributes[block.BlockType].density;

 

You could cache the attributes reference in the block if you so desire to speed up lookup and avoid the dict lookup each time. This attributes could also include your Tile if for stone if the x/y are just fixed texture offsets into a tilemap and always 3/1 for stone.

 

If you go this route make the attribute objects immutable otherwise a mistake like changing the density of rock would effect all rock blocks :)

 

The problem is, I want certain blocks to have different behaviors and functions. That's why I'm having trouble deciding the best way to do this.

Share this post


Link to post
Share on other sites

 

The problem is, I want certain blocks to have different behaviors and functions. That's why I'm having trouble deciding the best way to do this.

I think you somewhat mix up what "data" and "functionality" is :unsure:, hence you are trying to solve problems with polymorphism which do not even require it (meaning: it is overkill). This is pretty much detrimental for multiple reasons, since it clutters/complicates your code, and too much virtual calls, especially unnecessary ones, may become a performance hit (not a big one, but still)...
If a problem does not require polymorphism do not make the function virtual. If later it turns out, that some specialization is required you can change it than, or you can come up with an entirely different design based on the new requirements (maybe even more efficient/elegant based on the new facts/requirements).
Looking at the member functions of the "Block" class, clearly non of the methods seem to be doing stuff that should be or need-to-be overridden by specific Block implementations...
The "TexturePosition" specialization example you posted in the "BlockStone" class is a pretty bad one too. The funtion does not modify the old functionality at all, it simply does the same, returning data. Indeed a different tile, but it still simply returns a tile, which screams for it's own field/property instead, and not a virtual function.

On the other hand, what ferrous proposed is actually what you need. It is usually called "Type object pattern" or something pretty similar. Here is good summary (and example implementation) about this design pattern:
http://gameprogrammingpatterns.com/type-object.html

And before you think it will limit you in giving specializations/behaviors for each block type, it will not!
Here is a simple implementation for your case:

// I could not identify which fields/properties compose the "actual state" of your blocks...
// you have to separate the "state", and "description" of the blocks, since the "description"
// belongs to the "Type" of the block, and not to each instance of the "Block" object!
// in your current implementation you have a myriad of redundant data, wasting an insane amount of memory.
// so the fields in this class are based on guesses made upon reading your code.

// this class "describes" a specific block, like: Rock, Sand, Water etc...
public class BlockType
{
    public int temperature;
    public bool light;
    public int density;
    public string blockName;
    public Tile tile;
    
    // it can hold behaviours too, specific to a given block type
    // like in case of water or magma you could implement different collision reactions etc...
    // simply you specialize this class: public class WaterBlockType : BlockType { ... }
}

public class Block
{
    public int life;
    public BlockType type;
    
    // behavior specific to block instances...
}

// creating block types:
var typeStone = new BlockType
{
    temperature = 0,
    light = false,
    density = 400,
    blockName = "Stone",
    tile = new Tile { x = 3, y = 1 }
};
var typeSand = new BlockType
{
    temperature = 0,
    light = false,
    density = 100,
    blockName = "Sand",
    tile = new Tile { x = 1, y = 0 }
}

// creating blocks:
var stone1 = new Block { life = 10, type = typeStone };
var stone2 = new Block { life = 15, type = typeStone };
var sand1 = new Block { life = 2, type = typeSand };
var sand2 = new Block { life = 1, type = typeSand };
// ...

// your block instances can have methods/behaviors like:
stone1.DamagedByPlayer(2);
stone2.ReinforcedByPlayer(1);

// once you want to implement a specific behavior, which are specific to a block type
// (like water acts this way, magma acts that way...)
// you can still implement and execute behaviors specific to the type of the blocks:
sand1.type.CreateBlockdata(chunck, 10, 20, 30, mesh);
sand2.type.HandleCollisionEventWithPlayer(player);

// here is another neat trick:
// with this system, you can easily create a library of block types, and a block "Factory":
public class BlockFactory
{
    private Dictionary<string, BlockType> types = new Dictionary<string, BlockType>();
    
    public void AddType(BlockType type) {
        this.types.Add(type.blockName, type);
    }
    
    public Block CreateType(string typeName, int life) {
        return new Block { life = life, type = this.types[typeName] };
    }
}

var factory = new BlockFactory();
factory.AddType(typeStone);
factory.AddType(typeSand);

var stone3 = factory.CreateType("Stone", 1);
var sand3 = factory.CreateType("Sand", 10);

So to sum it up in a short sentence: you have to separate data from the blocks which are not actual state relevant to block instances, but relevant to a specific block types (like water, magma, stone etc...), and you have to do the same with behaviors too!
Think of these as two separate concepts, so they most probably should be different classes/objects (an object which represents a block in the world, and an object which describes a given type of blocks, like water or stone) :wink:.

Share this post


Link to post
Share on other sites

 

 

The problem is, I want certain blocks to have different behaviors and functions. That's why I'm having trouble deciding the best way to do this.

I think you somewhat mix up what "data" and "functionality" is :unsure:, hence you are trying to solve problems with polymorphism which do not even require it (meaning: it is overkill). This is pretty much detrimental for multiple reasons, since it clutters/complicates your code, and too much virtual calls, especially unnecessary ones, may become a performance hit (not a big one, but still)...
If a problem does not require polymorphism do not make the function virtual. If later it turns out, that some specialization is required you can change it than, or you can come up with an entirely different design based on the new requirements (maybe even more efficient/elegant based on the new facts/requirements).
Looking at the member functions of the "Block" class, clearly non of the methods seem to be doing stuff that should be or need-to-be overridden by specific Block implementations...
The "TexturePosition" specialization example you posted in the "BlockStone" class is a pretty bad one too. The funtion does not modify the old functionality at all, it simply does the same, returning data. Indeed a different tile, but it still simply returns a tile, which screams for it's own field/property instead, and not a virtual function.

On the other hand, what ferrous proposed is actually what you need. It is usually called "Type object pattern" or something pretty similar. Here is good summary (and example implementation) about this design pattern:
http://gameprogrammingpatterns.com/type-object.html

And before you think it will limit you in giving specializations/behaviors for each block type, it will not!
Here is a simple implementation for your case:

// I could not identify which fields/properties compose the "actual state" of your blocks...
// you have to separate the "state", and "description" of the blocks, since the "description"
// belongs to the "Type" of the block, and not to each instance of the "Block" object!
// in your current implementation you have a myriad of redundant data, wasting an insane amount of memory.
// so the fields in this class are based on guesses made upon reading your code.

// this class "describes" a specific block, like: Rock, Sand, Water etc...
public class BlockType
{
    public int temperature;
    public bool light;
    public int density;
    public string blockName;
    public Tile tile;
    
    // it can hold behaviours too, specific to a given block type
    // like in case of water or magma you could implement different collision reactions etc...
    // simply you specialize this class: public class WaterBlockType : BlockType { ... }
}

public class Block
{
    public int life;
    public BlockType type;
    
    // behavior specific to block instances...
}

// creating block types:
var typeStone = new BlockType
{
    temperature = 0,
    light = false,
    density = 400,
    blockName = "Stone",
    tile = new Tile { x = 3, y = 1 }
};
var typeSand = new BlockType
{
    temperature = 0,
    light = false,
    density = 100,
    blockName = "Sand",
    tile = new Tile { x = 1, y = 0 }
}

// creating blocks:
var stone1 = new Block { life = 10, type = typeStone };
var stone2 = new Block { life = 15, type = typeStone };
var sand1 = new Block { life = 2, type = typeSand };
var sand2 = new Block { life = 1, type = typeSand };
// ...

// your block instances can have methods/behaviors like:
stone1.DamagedByPlayer(2);
stone2.ReinforcedByPlayer(1);

// once you want to implement a specific behavior, which are specific to a block type
// (like water acts this way, magma acts that way...)
// you can still implement and execute behaviors specific to the type of the blocks:
sand1.type.CreateBlockdata(chunck, 10, 20, 30, mesh);
sand2.type.HandleCollisionEventWithPlayer(player);

// here is another neat trick:
// with this system, you can easily create a library of block types, and a block "Factory":
public class BlockFactory
{
    private Dictionary<string, BlockType> types = new Dictionary<string, BlockType>();
    
    public void AddType(BlockType type) {
        this.types.Add(type.blockName, type);
    }
    
    public Block CreateType(string typeName, int life) {
        return new Block { life = life, type = this.types[typeName] };
    }
}

var factory = new BlockFactory();
factory.AddType(typeStone);
factory.AddType(typeSand);

var stone3 = factory.CreateType("Stone", 1);
var sand3 = factory.CreateType("Sand", 10);

So to sum it up in a short sentence: you have to separate data from the blocks which are not actual state relevant to block instances, but relevant to a specific block types (like water, magma, stone etc...), and you have to do the same with behaviors too!
Think of these as two separate concepts, so they most probably should be different classes/objects (an object which represents a block in the world, and an object which describes a given type of blocks, like water or stone) :wink:.

 

 

Thank you! That was extremely helpful, and I'm implementing it now.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this  

  • Announcements

  • Forum Statistics

    • Total Topics
      628333
    • Total Posts
      2982130
  • Similar Content

    • By BAG Labs
      Mobile SoS

      Platform: Android
      Genre: Board
      Link: Google Play
       
      This games sharpen memory and test your strategies to place S-O-S pattern within time limit and serve 3 difficulties as Easy, Normal, and Hard.

      Goals of the game is to put S-O-S words in patterns (Horizontal, Vertical, and Diagonal) alternately with enemy.
       
      Features:
      Single Player Multiplayer Achievements Leaderboards  
       
      Screenshot:

       

       

       
      Link: Google Play
       
      Please help us improve this game with review
    • By abarnes
      Hello!
      I am a game development student in my second year of a three year program and I would like to start building my portfolio. I was thinking of creating some games to show what I can do to potential employers since I wont have any work related experience when I graduate. But as I'm sure you all know there are tons of ways to approach developing/designing a game and I'm curious if anyone had any insight as to any "standards" that come with this? Is it okay to use game engines like Unity, Unreal, Game Maker etc? Or would it be better to make a game from scratch to better show case your skills? Any and all advice will be greatly appreciated!
    • By Hilster
      Hello 2D Artists,
      I've started making a 2D Puzzle Adventure game for mobile and I'm looking for someone who would want in on creating assets for the game. The core of the programming is pretty much complete, you can walk within the grid laid out and push boxes, when there is an object on top of a pressure pad it will activate the linked objects or if there is one object with multiple linked pressure pads it requires you to activate all points for the object to become active. 

      The level iteration for the game is quick and simple, a Photoshop file that is made of individual pixels that represents objects is put into the game and it creates the level out of those pixels with the assigned objects.
      The objects that need sprites created so far is the character, box, pressure pad, door, trap door, the walls, the stairs and the tiled background.
      I intend to add more objects so the amount I'd like to add will be extended.
      My motivations for posting here is to have something that looks nice to be able to display on my portfolio, so if you're looking for a working game that you can place your art into and improve the look of your portfolio then we're in business.
      Please reply with a few past examples of your art below and I'll be in touch!
    • By thefollower
      Hi
      I have set up my TcpClient to connect to my server and that works fine. But i am a bit confused how i read messages from the network stream with it continuously running via async, without ever closing the connection ?
      My TcpClient Manager class has:
       
      public async Task<bool> Connect(string address, int port) { try { await _tcpClient.ConnectAsync(address, port); IsConnected = true; return true; } catch(Exception e) { Debug.Log(e); return false; } } public async Task<int> Read(byte[] readBuffer) { if (!IsConnected) return -1; using (var networkStream = _tcpClient.GetStream()) { try { var bytesRead = await networkStream.ReadAsync(readBuffer, 0, readBuffer.Length); return bytesRead; } catch (Exception e) { Debug.Log(e); IsConnected = false; return -1; } } }  
      So i thought to just run a co-routine and call Read constantly to get the most recent message, but that doesn't make much sense to me since a co-routine would be blocked with the await. How is this actually done? The MS Docs don't have very good Async examples with the TcpClient class so i don't know fully get how to keep calling Read correctly.
  • Popular Now