Suppose I have the following 2 classes:
extends Resource
class_name MenuItem
export var food: Texture = preload("Burger.png")
export var drink: Texture = preload("Soda.png")
extends Node
class_name Meal
# "Public" variable
var menu_item: MenuItem setget _set_menu_item
# "Private" variables
onready var _food: Sprite = $Food
onready var _drink: Sprite = $Drink
func _set_menu_item(value: MenuItem) -> void:
menu_item = value
_food.texture = menu_item.food
_drink.texture = menu_item.drink
The problem is that I bypass the mutator if I cache the menu_item property:
extends Node
onready var meal: Meal = $Meal
onready var menu_item: MenuItem = meal.menu_item
func _ready() -> void:
# Will trigger `_set_menu_item` and update `_food.texture`
meal.menu_item.food = load("Salad.png")
# Won't trigger `_set_menu_item` and won't update `_food.texture`
menu_item.food = load("Soup.png")
While keeping both classes mutable, how would you ensure that Meal always behaves as expected and updates its sprites when menu_item is changed?
I would do that using custom signal, which I would emit from MenuItem's member mutator and connect to a callback that updates the private Meal's value.
extends Resource
class_name MenuItem
# I use floating point "weight" instead of your Texture variables "food" and "drink"
# But everything should be same for Textures
export var weight: float = 0.0 setget _set_weight
signal weight_changed
func _set_weight(value: float) -> void:
if weight != value:
weight = value
emit_signal("weight_changed")
extends Node
class_name Meal
# "Public" variable
var menu_item: MenuItem = MenuItem.new()
# "Private" variables
var _weigth: float = 0.0
func _ready():
menu_item.connect("weight_changed", self, "_set_weight")
func _set_weight():
_weigth = menu_item.weight
extends Node
onready var meal: Meal = $Meal
onready var menu_item: MenuItem = meal.menu_item
func _ready():
# Will trigger `_set_weight` and update `_weight`
meal.menu_item.weight = 2.0
# Will do that also
menu_item.weight = -2.0
Related
I'm having a rendering issue for 3d particles created with the flame particle editor in libgdx.
I'm using a particle system to render 3d particles created with flame, I followed the tutorial in the libgdx' official site: code taken from: , but when I try to render them, I get the following error:
Cannot invoke "com.badlogic.gdx.graphics.g3d.particles.batches.ParticleBatch.draw(com.badlogic.gdx.graphics.g3d.particles.renderers.ParticleControllerRenderData)" because "this.batch" is null
Here's my code:
class MyGame : ApplicationAdapter(){
//3d stuff
lateinit var cam: PerspectiveCamera
lateinit var camController: CameraInputController
lateinit var modelBatch: ModelBatch
lateinit var environment: Environment
lateinit var model: Model
lateinit var mb:ModelBuilder
//lateinit var ground:ModelInstance
lateinit var ecs:World
//creating the bullet world
lateinit var broadphase: btBroadphaseInterface
lateinit var dynamicsWorld: btDynamicsWorld
lateinit var constraintSolver: btConstraintSolver
lateinit var collisionConfig: btCollisionConfiguration
lateinit var dispatcher: btDispatcher
lateinit var contactListener: MyContactListener
//debug drawer
lateinit var debugDrawer: DebugDrawer
//particle effects
lateinit var particleSystem:ParticleSystem
lateinit var pointSpriteBatch:PointSpriteParticleBatch
override fun create() {
Bullet.init()
//initializing 3d stuff
mb = ModelBuilder()
initializeWorld()
initializeBullet()
ecs= world {
injectables {
add(mb)
add(modelBatch)
add(cam)
add(camController)
add(environment)
add(dynamicsWorld)
add(debugDrawer)
add(contactListener)
}
components {
onAdd(BulletComponent, BulletComponent.onAdd)
onRemove(BulletComponent, BulletComponent.onRemove)
}
systems {
//the model system works just fine!
add(ModelSystems())
//this is going to be fun
add(BulletSystem())
}
}
with(ecs){//creating the ground
mb.begin()
//ok so for every model you create you giv it an id (makes everything more flexible)
mb.node().id="ground"
mb.part(
"ground",
GL20.GL_TRIANGLES,
(VertexAttributes.Usage.Position or VertexAttributes.Usage.Normal).toLong(),
Material(
ColorAttribute.createDiffuse(Color.RED)
)
)
.box(0f, 0f, 0f, 5f, 1f, 5f)
model=mb.end()
var motionState=MotionState(ModelInstance(model).transform)
val ground=entity {
//it+=ModelComponent(ModelInstance(model))
it+=BulletComponent(motionState, btBoxShape(Vector3(2.5f, 0.5f, 2.5f)),0f)
}
}
}
private fun initializeBullet() {
collisionConfig = btDefaultCollisionConfiguration()
dispatcher = btCollisionDispatcher(collisionConfig)
broadphase = btDbvtBroadphase()
//setting the dynamic world
constraintSolver= btSequentialImpulseConstraintSolver()
dynamicsWorld= btDiscreteDynamicsWorld(dispatcher,broadphase, constraintSolver, collisionConfig)
dynamicsWorld.gravity= Vector3(0f, -1.8f, 0f)
contactListener = MyContactListener()
debugDrawer=DebugDrawer().apply {
debugMode= btIDebugDraw.DebugDrawModes.DBG_DrawWireframe
}
dynamicsWorld.debugDrawer=debugDrawer
}
fun initializeWorld(){
modelBatch = ModelBatch()
environment = Environment()
environment.set(ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f))
environment.add(DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f))
cam = PerspectiveCamera(
67f, Gdx.graphics.width.toFloat(), Gdx.graphics.height
.toFloat()
)
cam.position[3f, 7f] = 10f
cam.lookAt(0f, 4f, 0f)
cam.near = 1f
cam.far = 300f
cam.update()
camController = CameraInputController(cam)
Gdx.input.inputProcessor = camController
//initializing particle effects
particleSystem=ParticleSystem()
pointSpriteBatch = PointSpriteParticleBatch()
pointSpriteBatch.setCamera(cam)
particleSystem.add(pointSpriteBatch)
//adding the particle effect to an asset manager
var assets=AssetManager()
var loadParam=ParticleEffectLoader.ParticleEffectLoadParameter(particleSystem.batches)
assets.load("myfirst3dparticles.pfx",
ParticleEffect::class.java, loadParam)
assets.finishLoading()
//passing the particle effect from the asset manager to the particle system
val originalEffect: ParticleEffect = assets.get("myfirst3dparticles.pfx")
// we cannot use the originalEffect, we must make a copy each time we create new particle effect
val effect = originalEffect.copy()
effect.init()
effect.start() // optional: particle will begin playing immediately
particleSystem.add(effect)
}
override fun render() {
Gdx.gl.glClearColor(0f, 0f, 0f, 1f)
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT or GL20.GL_DEPTH_BUFFER_BIT)
Gdx.gl.glEnable(GL20.GL_TEXTURE_2D)
//always update the camera if you're gonna make changes to it
//cam.update()
camController.update()
modelBatch.begin(cam)
//modelBatch.render(ground, environment)
//setting particles to be rendered
particleSystem.update()
particleSystem.begin()
particleSystem.draw()
particleSystem.end()
modelBatch.render(particleSystem)
ecs.update(Gdx.graphics.deltaTime)
modelBatch.end()
}
I am working through the tutorial on android developer central. One of the challenges is to extend the tutorial dice roller to include 2 dice. I tried creating a 2nd instance of my dice with
var dice2 = Dice(6) using the same class definition that I did for my first die. However, both return the same value. So I tried creating a 2nd class definition for Dice2 and created an instance of that with `var dice2 = Dice2(6). But I get the same result. Below is the full code.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rollButton: Button = findViewById(R.id.button)
rollButton.setOnClickListener { rollDice() }
//roll the dice onLoad
rollDice()
}
private fun rollDice() {
//create two 6 sided die and rolls them
val dice = Dice(6)
val diceRoll = dice.roll()
val dice2 = Dice2(6)
val diceRoll2 = dice2.roll()
//assign the ImageViews called imageView and imageView2 to the variables diceImage and diceImage2
val diceImage: ImageView = findViewById(R.id.imageView)
val diceImage2: ImageView = findViewById(R.id.imageView2)
//update the die graphic based on the outcome of the dice roll
//assigns the resource id to a variable called drawableResource
val drawableResource = when(diceRoll) {
1 -> R.drawable.dice_1
2 -> R.drawable.dice_2
3 -> R.drawable.dice_3
4 -> R.drawable.dice_4
5 -> R.drawable.dice_5
else -> R.drawable.dice_6
}
/* do the same for the 2nd die */
val drawableResource2 = when(diceRoll2) {
1 -> R.drawable.dice_1
2 -> R.drawable.dice_2
3 -> R.drawable.dice_3
4 -> R.drawable.dice_4
5 -> R.drawable.dice_5
else -> R.drawable.dice_6
}
/*Update the die image source based on the variable
convert the die roll results from int to string and update the content description for
the die image
*/
diceImage.setImageResource(drawableResource)
diceImage.contentDescription = diceRoll.toString()
diceImage2.setImageResource(drawableResource)
diceImage2.contentDescription = diceRoll2.toString()
}
}
//create a class for an object called Dice & defines a roll function with random number
class Dice(private val numSides: Int) {
fun roll(): Int {
return (1..numSides).random()
}
}
class Dice2(private val numSides: Int) {
fun roll(): Int {
return (1..numSides).random()
}
}
Any help is greatly appreciated.
It seems you made a mistake in this line:
diceImage2.setImageResource(drawableResource)
It should be:
diceImage2.setImageResource(drawableResource2)
Try diceImage2.setImageResource(drawableResource2) instead of diceImage2.setImageResource(drawableResource). But better:
you definitely don't need Dice2; use val dice2 = Dice(6) as you had originally, or even just val diceRoll2 = dice.roll() again.
you'd avoid even a chance to make that mistake if you made a function
fun showDiceResult(viewId: Int, rollResult: Int) {
// get view by id
// set image resource
// set content description
}
and then called it twice
showDiceResult(R.id.imageView, diceRoll)
showDiceResult(R.id.imageView2, diceRoll2)
I have a complex object that I want to display in a textfield. This is working fine with a stringBinding. But I don't know how to make it two-way so that the textfield is editable.
package com.example.demo.view
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import tornadofx.*
class MainView : View("Hello TornadoFX") {
val complexThing: Int = 1
val complexProperty = SimpleObjectProperty<Int>(complexThing)
val complexString = complexProperty.stringBinding { complexProperty.toString() }
val plainString = "asdf"
val plainProperty = SimpleStringProperty(plainString)
override val root = vbox {
textfield(complexString)
label(plainProperty)
textfield(plainProperty)
}
}
When I run this, the plainString is editable and I see the label change because the edits are going back into the property.
How can I write a custom handler or what class do I need to use to make the stringBinding be read and write? I looked through a lot of the Property and binding documentation but did not see anything obvious.
Ta-Da
class Point(val x: Int, val y: Int) //You can put properties in constructor
class PointConverter: StringConverter<Point?>() {
override fun fromString(string: String?): Point? {
if(string.isNullOrBlank()) return null //Empty strings aren't valid
val xy = string.split(",", limit = 2) //Only using 2 coordinate values so max is 2
if(xy.size < 2) return null //Min values is also 2
val x = xy[0].trim().toIntOrNull() //Trim white space, try to convert
val y = xy[1].trim().toIntOrNull()
return if(x == null || y == null) null //If either conversion fails, count as invalid
else Point(x, y)
}
override fun toString(point: Point?): String {
return "${point?.x},${point?.y}"
}
}
class MainView : View("Hello TornadoFX") {
val point = Point(5, 6) //Probably doesn't need to be its own member
val pointProperty = SimpleObjectProperty<Point>(point)
val pc = PointConverter()
override val root = vbox {
label(pointProperty, converter = pc) //Avoid extra properties, put converter in construction
textfield(pointProperty, pc)
}
}
I made edits to your converter to "account" for invalid input by just returning null. This is just a simple band-aid solution that doesn't enforce correct input, but it does refuse to put bad values in your property.
This can probably be done more cleanly. I bet there is a way around the extra property. The example is fragile because it doesn't do input checking in the interest of keeping it simple. But it works to demonstrate the solution:
class Point(x: Int, y: Int) {
val x: Int = x
val y: Int = y
}
class PointConverter: StringConverter<Point?>() {
override fun fromString(string: String?): Point? {
val xy = string?.split(",")
return Point(xy[0].toInt(), xy[1].toInt())
}
override fun toString(point: Point?): String {
return "${point?.x},${point?.y}"
}
}
class MainView : View("Hello TornadoFX") {
val point = Point(5, 6)
val pointProperty = SimpleObjectProperty<Point>(point)
val pointDisplayProperty = SimpleStringProperty()
val pointStringProperty = SimpleStringProperty()
val pc = PointConverter()
init {
pointDisplayProperty.set(pc.toString(pointProperty.value))
pointStringProperty.set(pc.toString(pointProperty.value))
pointStringProperty.addListener { observable, oldValue, newValue ->
pointProperty.set(pc.fromString(newValue))
pointDisplayProperty.set(pc.toString(pointProperty.value))
}
}
override val root = vbox {
label(pointDisplayProperty)
textfield(pointStringProperty)
}
}
I was creating a very simple kotlin program and saw a weird behavior for parent class.
The code is:
fun makeSalt(name:String) = Spice(name, "non-spicy")
fun main(args: Array<String>) {
var salt : Spice = Spice("salt", "non-spicy")
println("Salt heat = ${salt.heat}")
val spicelist = listOf<Spice>(
Spice("salt", "non-spicy"),
Spice("turmeric", "mild"),
Spice("Pepper", "hot"),
Spice("Chilli", "hot"),
Spice("Sugar", "non-spicy")
)
val mildSpices = spicelist.filter{it.heat <=5}
val salt2 = makeSalt("rock salt")
val bhoot : SubSpice = SubSpice("bhoot", "hot")
}
open class Spice(open var name:String, open var spiciness:String = "mild" ){
var heat : Int = 5
get() = when (spiciness){
"mild"->5
"hot"->10
"non-spicy"->1
else -> 0
}
init{
if(spiciness === null) {println("spiciness is null")}
else println("Spiciness of ${name} = ${spiciness}; heat = ${heat}")
}
}
class SubSpice(override var name:String, override var spiciness:String = "hot") : Spice(name, spiciness){
}
When I execute this program, the output is:
Spiciness of salt = non-spicy; heat = 1
Salt heat = 1
Spiciness of salt = non-spicy; heat = 1
Spiciness of turmeric = mild; heat = 5
Spiciness of Pepper = hot; heat = 10
Spiciness of Chilli = hot; heat = 10
Spiciness of Sugar = non-spicy; heat = 1
Spiciness of rock salt = non-spicy; heat = 1
spiciness is null
As you can see, when I created an object of the sub-class, the variable spiciness of parent class becomes null. Can someone explain why is that? I expected it to be null since it also has default parameter of "mild"
You are you using open var when you're not overriding any getter/setter methods.
You're introducing weird initialization conflict since Spice.init (parent class constructor) is invoked before SubSpice.init and by overriding the fields they are no longer initialized alongside parent constructor - instead they will be available once Subspice is constructed.
Remove open keyword from variables in parent class and override var in child constructor, that way fields will be properly initialized in Spice class and its init block should run successfully.
To add extra information to Pawel's answer:
If you are overridding properties using open, the init block in the base class will run before the derived properties have been initialized.
See docs: https://kotlinlang.org/docs/reference/classes.html#derived-class-initialization-order
So if we change the code like Pawel suggested, we end up with something like this:
fun main(args: Array<String>) {
Spice("salt", "non-spicy")
Spice(name="spice")
SubSpice(name = "bhoot", spiciness = "hot")
SubSpice(name="subspice")
}
open class Spice(var name:String, var spiciness : String = "mild" ) {
var heat : Int = 5
get() = when (spiciness){
"mild"->5
"hot"->10
"non-spicy"->1
else -> 0
}
init{
if(spiciness === null) {println("spiciness is null")}
else println("Spiciness of ${name} = ${spiciness}; heat = ${heat}")
}
}
class SubSpice(name: String, spiciness : String = "hot") : Spice(name, spiciness) {
}
Which outputs:
Spiciness of salt = non-spicy; heat = 1
Spiciness of spice = mild; heat = 5
Spiciness of bhoot = hot; heat = 10
Spiciness of subspice = hot; heat = 10
I wish to use several classes interchangeably, all of them implementing a method "add", so I start to write a trait:
trait CanAdd{
def add(that: CanAdd): CanAdd
}
then implement my classes and hit a problem: I cannot add any "CanAdd" with any other, they need to be of the same class. I workaround the problem with some ugly code relying on "isInstanceOf":
class A(val value: Int) extends CanAdd{
def add(that: CanAdd): CanAdd = {
if(!that.isInstanceOf[A]) sys.error("")
val thatA: A = that.asInstanceOf[A]
new A(value + thatA.value)
}
}
class B(val value: Boolean) extends CanAdd{
def add(that: CanAdd): CanAdd = {
if(!that.isInstanceOf[B]) sys.error("")
val thatB: B = that.asInstanceOf[B]
new B(value ^ thatB.value)
}
}
Finally, I use the classes as follow:
class User(val stuff: Array[CanAdd]) {
def add(that: User): User = {
assume(stuff.length==that.stuff.length)
val out = new Array[CanAdd](stuff.length)
for( i <- 0 until stuff.length) out(i) = stuff(i).add(that.stuff(i))
new User(out)
}
}
val u1=new User(Array(new A(0)))
val u2=new User(Array(new B(false)))
val u3 = u1.add(u1)
val u4 = u1.add(u2) //should fail, ideally, should not even compile
I don't like it because first it is a burden to write the boiler plate code with "isInstanceOf" and second because it fails at run time instead of compile time.
My question: how would you do that, having in mind some plan to implement many more methods than just "add" and maybe to implement few other classes with very different internal representations ?
trait CanAdd[T <: CanAdd[T]] {
def add(a: T): T
}
class A(val value:Int) extends CanAdd[A] {
def add(that:A) = new A(value+that.value)
}
class B(val value:Boolean) extends CanAdd[B] {
def add(that:B) = new B(value ^ that.value)
}
class User[X <: CanAdd[X] : Manifest](val stuff:Array[X]) extends CanAdd[User[X]]{
def add(that:User[X]):User[X] = {
assume(stuff.length==that.stuff.length)
new User(stuff.zip(that.stuff).map(t => t._1.add(t._2)) toArray)
}
}
val u1 = new User(Array(new A(0)))
val u2 = new User(Array(new B(false)))
val u3 = u1.add(u1)
val u4 = u1.add(u2) // compile error: type mismatch