Update: I created a git repository on GitHub for this series. Check it out! The Repo
The last tutorial was quite intense to read and boring because of the terms. This one, I'll start writing a block right away, but with some nice scalatic taste in it! Note this is also going to be very long since I introduce much more than making a simple block. Read to the end, I guarantee that you'll have fun!
Tutorials teaching you how to make blocks are everywhere. OrangeTutorial has a quite good one. However, many of them suffer from being too vague and overlook some potential problems. It is a compromise to the fact that many readers have no programming knowledge at all. Since people interested in reading this tutorial may already dragged themselves through the last one, I'll go ahead and assume you understand the basic idea of OOP like classes and instances. I didn't get into one of the most important OOP concept of inheritance, so this is the place to introduce.
The process of making a block is distilled to the following steps: 1). Make a subclass of Block class. 2). do something in the constructor to setup the block to have correct names, tectures, and appear in a creative tab. 3). new an instance of the block and store it somewhere. 4). use GameRegistry to register the newed instance.
explanation of inheritance
What's a subclass? OOP is all about designing, so you write a lot of blueprints to describe the objects you are gonna make. It is a very labor intensive task, so people came up with inheritance. Inheritance basically is an automatic copy-paste, when a class A inherits from another class B, we call A a subclass of B. By inheriting from B, A automatically gets everything that B has. Be it variables defined in B, and all the methods too. the extends keyword in both java and scala means inheritance. Go back and check OrangTutorial's block page. the OrangeBlock extends Block from minecraft, so it is a subclass of the Block class (Block is the superclass of OrangeBlock). Because subclasses gets the thing from the superclass for free, so that OrangBlock class gets the "super" constructor, "setCreativeTab", "setBlockName" and "setBlockTextureName" automatically.
I don't know if anybody noticed or not, but every time you want a new block, you have to create a separate subclass of Block. Much worse, you have to call setCreativeTab and all those methods again. that's a lot of typing! There is yet another pitfall to this approach. What if Mojang had a meeting and decided that they want to call "setCreativeTab" as "setAwesomeTab" instead? If you have 100 blocks, you'll have to update 100 files when you update! This might not be a problem for user of IDEs with the refactoring functionality, but if the parameters of "setCreativeTab" changed from a CreativeTab to CreativeTab and the block? You don't just update the name, you have to update how it is used, and eclipse's refactoring definitely will not help!
If you reason thoroughly, GameRegistry registers the object, not the class. So, if you want a different block, why not create another instance? Instead of having 100 classes, why can't we have only one class but 100 objects? If you feel "Aha!", then welcome to the next level of programming .
The main code for this tutorial. Modified from OrangeTutorial. I only show main points, so things like package and import will be ignored. I think most people uses eclipse, so when you see redlines, press "ctrl+shift+o", and it will automatically handle import for you.
class BaseBlock(nam: String, mat: Material) extends Block(mat){
setBlockName(nam)
setCreativeTab(CreativeTabs.tabBlock)
}
Very short isn't it? You can already appreciate scala!
And "gradle build", "gradle runClient". Create a creative world, and Tada! you have two blocks in the blocks creative tab, while you only have one class for the block!
explanation:
As you have guessed, the lines in preInit did the trick. You initialized two different objects by calling "new BaseBlock" twice. the "blockA" and Material.rock passed in are parameters for the constructor. As you can see, in the BaseBlock definition, I tell scala explicitly that I wanted two parameters for the constructor. "nam" is a String type, corresponding to "blockA"; mat is of type Material, corresponding to Material.rock. The scala syntax for constructor is very different than java. in java, you have a method like definition that has the same name as the class itself, and that's the constructor (in java, our constructor would be "public BaseBlock(String nam, Material mat)"). In scala, the constructor is combined with the class definition itself, as our example shows "class BaseBlock(nam: String, mat: Material)". In java, if you want to pass a parameter to the Block class, you have to do it in the constructor and call super(mat). In scala, you just put it in the definition also, as you can see in "extends Block(mat)".
Tutorial continues to the second post as it gets really long. Finishing the OP, one can already make a block, so I think that's a good breaking point.
The "two object make two block" outcome is consistent with our observation before: GameRegistry registers the object, not the class. You can specify the textures too. Just add a parameter in the constructor that's "tex: String", and call setBlockTextureName(tex) in the constructor. Now you defined only one class, but can have as many blocks ingame as you want each with different names and textures. Just instantiate different objects with different parameters.
"But wait a second," you say. "I can see the benefit, but it's all because names and textures are data. What if we want different behaviors? You can't pass a function definition into the constructor!"
Scala has you covered. There are a lot of ways you can do that actually! Today, we are going to talk about a way that's not functional, introducing scala traits. traits:
For java programmers, think traits as interfaces with concrete implementations. Java 8 actually has that already. For OOP enthusiasts, you probably already know what's bad about multiple inheritance, and trait is a lightweight class that doesn't have the "diamond of death" problem. For non-programmers, think traits as usb sticks. You can plug usb sticks into a computer to add some functions to the computer. Same with traits, you can plug traits into classes so the class will have more functions. In scala, you add a trait also by the keyword "extends". If you also inherit from a superclass or has more than one trait, use the keyword "with". For example, "class A extends ClassB with Trait1 with Trait2 with ..." , or "class A extends Trait1 with Trait 2 with ...".
The definition of a scala trait is very humble. However, you can do much more than it looks like because two important features we are going to use here: contruction time mix-in and layered traits.
Let's first start by looking at the problem. Let's add some more sugar to our BaseBlock class by overriding the onBlockActivated method inherited from Block (on topics about overriding, overloading and function signatures, go check wikipedia). code:
The code above basically means dropping one diamond each time you right click on the block. Nice cheating block to make you rich fast isn't it? However, the line "world.spawnEntityInWorld" is hard coded. You can't pass in a parameter to the method "onBlockActivated" to set which item you want to drop, because the signature cannot be modified when overriding a method. Well, you can passing a parameter in the constructor, and store it as a field in the class, and then access it from within "onBlockActivated". However, that way, you can only customize to a very limited extent. What if you want the block to also play a particle effect or a sound file? Now you miss the old "one block one class" way of doing things. Fear not, using the scala traits' nice features can make your life easier! Follow me closely as the next part will be a little intense.
We need a functionality to put the code inside "onBlockActivated" somewhere else so that "somewhere else" could change freely. If you are smart enough, you already have the answer: put it inside another method so we have total control of the signature and behavior ("delegation" is the word you need to google). Let's do that:
No surprise that the trait keyword defines a trait. we just return true here without doing anything useful. mix-in the trait into our BaseBlock definition:
class BaseBlock(nam: String, mat: Material) extends Block(mat) with RClickable
and the above code compiles. Enter the game, right click on the block, it doesn't do anything. Now we need to add concrete traits that actually do something. Make a new trait named "DiamondClicker", and extend the base trait "RClickable", then we override "executeRightClick" to make it actually do something:
Java programmers probably get very confused here. Why do I call "super.executeRightClick"? Doesn't that method just return true and does nothing else? Hold your breath, as this concept of layered trait will be introduced later. We now go to construction time mix-in first .
If you don't mix-in DiamondClicker anywhere, you will not get a diamond no matter how hard you right-click your blocks. From what we talked before, one's instinct is to mix-in at the definition time. so we make something like "class BaseBlock(nam: String, mat: Material) extends Block(mat) with DiamondClicker".
However, this way, all our objects instantiated from BaseBlock will drop diamonds when right-clicked! If you don't want diamond spamming, this is not good! Scala traits can actually be mixed-in at construction time! How neat is that! so, we leave our BaseBlock definition as-is, and mix-in at construction time. In your preInit method, change the code like this:
run your game, and right-click on blockA, you will get diamonds! Then right-click on blockB, nothing! Are your blood boiling because of excitement? Me too. This neat feature allows you to write a lot less code than that of java, and at the same time be very flexible! Let's push the possibility a little bit further. Now, instead of just spamming diamonds, you want to spam fireworks too. From previous experience, you write a ParticleClicker trait instinctively:
Now you mix-in it to blockB: "GameRegistry.register(new BaseBlock("blockB", Material.rock) with ParticleClicker, "blockB")". Now you have a block that shoot fireworks when right-clicked! However, your imagination never stops, and you want a block that spawns both diamonds and fireworks, how do you do that? This is where our "super.executeRightClick" line comes into play. Usually, if you want to combine behaviors, you have to write a new trait that has two lines of code, both "world.spawnEntityInWorld" and "world.spawnParticle". Scala releases you from that pain. Add this line to your preInit method:
@Mod.EventHandler
def preInit(e: FMLPreInitializationEvent) {
GameRegistry.register(new BaseBlock("blockA", Material.rock) with DiamondClicker, "blockA")
GameRegistry.register(new BaseBlock("blockB", Material.rock) with ParticleClicker, "blockB") GameRegistry.register(new BaseBlock("blockC", Material.rock) with ParticleClicker with DiamondClicker, "blockC")
}
and Tada! you get yourself a new blockC that spams diamonds AND plays fireworks without writing any new class definitions! This is the power of layered trait. The call to "super.executeRightClick" doesn't call the base trait "RClickable", but calls the trait before it! In our blockC, when right clicked, we first call DiamondClicker's executeRightClick, and then call ParticleClicker's executeRightClick. Wow! How smart is scala! This way, we can chain the calls where order matters, and mix&match to create brand new objects on the fly. If you want to make 100 blocks each doing different stuff using the old way, you'll have to write 100 classes. With scala, you can write 10 traits + one base class, and yield 1024 different combinations in total!
End words:
scala is powerful and fun. Hope you learned something and be fascinated by the language after completing this tutorial!
The last tutorial was quite intense to read and boring because of the terms. This one, I'll start writing a block right away, but with some nice scalatic taste in it! Note this is also going to be very long since I introduce much more than making a simple block. Read to the end, I guarantee that you'll have fun!
Tutorials teaching you how to make blocks are everywhere. OrangeTutorial has a quite good one. However, many of them suffer from being too vague and overlook some potential problems. It is a compromise to the fact that many readers have no programming knowledge at all. Since people interested in reading this tutorial may already dragged themselves through the last one, I'll go ahead and assume you understand the basic idea of OOP like classes and instances. I didn't get into one of the most important OOP concept of inheritance, so this is the place to introduce.
The process of making a block is distilled to the following steps: 1). Make a subclass of Block class. 2). do something in the constructor to setup the block to have correct names, tectures, and appear in a creative tab. 3). new an instance of the block and store it somewhere. 4). use GameRegistry to register the newed instance.
explanation of inheritance
I don't know if anybody noticed or not, but every time you want a new block, you have to create a separate subclass of Block. Much worse, you have to call setCreativeTab and all those methods again. that's a lot of typing! There is yet another pitfall to this approach. What if Mojang had a meeting and decided that they want to call "setCreativeTab" as "setAwesomeTab" instead? If you have 100 blocks, you'll have to update 100 files when you update! This might not be a problem for user of IDEs with the refactoring functionality, but if the parameters of "setCreativeTab" changed from a CreativeTab to CreativeTab and the block? You don't just update the name, you have to update how it is used, and eclipse's refactoring definitely will not help!
If you reason thoroughly, GameRegistry registers the object, not the class. So, if you want a different block, why not create another instance? Instead of having 100 classes, why can't we have only one class but 100 objects? If you feel "Aha!", then welcome to the next level of programming
The main code for this tutorial. Modified from OrangeTutorial. I only show main points, so things like package and import will be ignored. I think most people uses eclipse, so when you see redlines, press "ctrl+shift+o", and it will automatically handle import for you.
class BaseBlock(nam: String, mat: Material) extends Block(mat){setBlockName(nam)
setCreativeTab(CreativeTabs.tabBlock)
}
Very short isn't it? You can already appreciate scala!
And in your main mod file's preInit method:
And "gradle build", "gradle runClient". Create a creative world, and Tada! you have two blocks in the blocks creative tab, while you only have one class for the block!
explanation:
The "two object make two block" outcome is consistent with our observation before: GameRegistry registers the object, not the class. You can specify the textures too. Just add a parameter in the constructor that's "tex: String", and call setBlockTextureName(tex) in the constructor. Now you defined only one class, but can have as many blocks ingame as you want each with different names and textures. Just instantiate different objects with different parameters.
"But wait a second," you say. "I can see the benefit, but it's all because names and textures are data. What if we want different behaviors? You can't pass a function definition into the constructor!"
Scala has you covered. There are a lot of ways you can do that actually! Today, we are going to talk about a way that's not functional, introducing scala traits.
traits:
The definition of a scala trait is very humble. However, you can do much more than it looks like because two important features we are going to use here: contruction time mix-in and layered traits.
Let's first start by looking at the problem. Let's add some more sugar to our BaseBlock class by overriding the onBlockActivated method inherited from Block (on topics about overriding, overloading and function signatures, go check wikipedia).
code:
The code above basically means dropping one diamond each time you right click on the block. Nice cheating block to make you rich fast isn't it? However, the line "world.spawnEntityInWorld" is hard coded. You can't pass in a parameter to the method "onBlockActivated" to set which item you want to drop, because the signature cannot be modified when overriding a method. Well, you can passing a parameter in the constructor, and store it as a field in the class, and then access it from within "onBlockActivated". However, that way, you can only customize to a very limited extent. What if you want the block to also play a particle effect or a sound file? Now you miss the old "one block one class" way of doing things. Fear not, using the scala traits' nice features can make your life easier! Follow me closely as the next part will be a little intense.
We need a functionality to put the code inside "onBlockActivated" somewhere else so that "somewhere else" could change freely. If you are smart enough, you already have the answer: put it inside another method so we have total control of the signature and behavior ("delegation" is the word you need to google). Let's do that:
now you need to find a place to define the executeRightClick method. Scala trait finally comes into play:
trait RClickable {def executeRightClick(world: World, x: Int, y: Int, z: Int) :Boolean = {true}
}
No surprise that the trait keyword defines a trait. we just return true here without doing anything useful. mix-in the trait into our BaseBlock definition:
and the above code compiles. Enter the game, right click on the block, it doesn't do anything. Now we need to add concrete traits that actually do something. Make a new trait named "DiamondClicker", and extend the base trait "RClickable", then we override "executeRightClick" to make it actually do something:
trait DiamondClicker extends RClickable {override def executeRightClick(world: World, x: Int, y: Int, z: Int): Boolean = {
world.spawnEntityInWorld(new EntityItem(world, x+0.5, y+1.0, z+0.5, new ItemStack(Items.diamond)))
super.executeRightClick(world, x, y, z)
}
}
Java programmers probably get very confused here. Why do I call "super.executeRightClick"? Doesn't that method just return true and does nothing else? Hold your breath, as this concept of layered trait will be introduced later. We now go to construction time mix-in first
If you don't mix-in DiamondClicker anywhere, you will not get a diamond no matter how hard you right-click your blocks. From what we talked before, one's instinct is to mix-in at the definition time. so we make something like "class BaseBlock(nam: String, mat: Material) extends Block(mat) with DiamondClicker".
However, this way, all our objects instantiated from BaseBlock will drop diamonds when right-clicked! If you don't want diamond spamming, this is not good! Scala traits can actually be mixed-in at construction time! How neat is that! so, we leave our BaseBlock definition as-is, and mix-in at construction time. In your preInit method, change the code like this:
run your game, and right-click on blockA, you will get diamonds! Then right-click on blockB, nothing! Are your blood boiling because of excitement? Me too. This neat feature allows you to write a lot less code than that of java, and at the same time be very flexible! Let's push the possibility a little bit further. Now, instead of just spamming diamonds, you want to spam fireworks too. From previous experience, you write a ParticleClicker trait instinctively:
trait ParticleClicker extends RClickable {override def executeRightClick(world: World, x: Int, y: Int, z: Int): Boolean = {
world.spawnParticle("fireworksSpark", x+0.5, y, z+0.5, 0.0, 1.0, 0.0)
super.executeRightClick(world, x, y, z)
}
}
Now you mix-in it to blockB: "GameRegistry.register(new BaseBlock("blockB", Material.rock) with ParticleClicker, "blockB")". Now you have a block that shoot fireworks when right-clicked! However, your imagination never stops, and you want a block that spawns both diamonds and fireworks, how do you do that? This is where our "super.executeRightClick" line comes into play. Usually, if you want to combine behaviors, you have to write a new trait that has two lines of code, both "world.spawnEntityInWorld" and "world.spawnParticle". Scala releases you from that pain. Add this line to your preInit method:
and Tada! you get yourself a new blockC that spams diamonds AND plays fireworks without writing any new class definitions! This is the power of layered trait. The call to "super.executeRightClick" doesn't call the base trait "RClickable", but calls the trait before it! In our blockC, when right clicked, we first call DiamondClicker's executeRightClick, and then call ParticleClicker's executeRightClick. Wow! How smart is scala! This way, we can chain the calls where order matters, and mix&match to create brand new objects on the fly. If you want to make 100 blocks each doing different stuff using the old way, you'll have to write 100 classes. With scala, you can write 10 traits + one base class, and yield 1024 different combinations in total!
End words:
scala is powerful and fun. Hope you learned something and be fascinated by the language after completing this tutorial!