Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Codea Craft and Documentation: Can we do better?

in Codea Craft Posts: 1,275

I'm thinking of starting an article series using Craft. I'd have kind of two angles. The first would be my usual learning by creating small programs that grow into larger programs. Some readers find those helpful in getting ideas of how to learn what the masses of code they are facing is all about.

The second notion would be to try to produce a sort of "linear" documentation of Craft, beginning at the beginning, and going as widely and as deeply as I can. Somewhere between a reference manual with examples, and a tutorial, I suppose.

Personally, I find the lack of any coherent narrative for Craft really irritating. There's all this power. There are all these very complicated examples, many of which just do things without really explaining how they do them. Somewhere in those examples, maybe, there's an example for what the reader might want to do, but it's embedded in a program that does something else almost entirely.

I'm hoping to help create something better, perhaps both in terms of example code, and written documentation. I'm sure that I'll need help finding examples, finding write ups, and so on. At this moment, I have no specific requests, but would appreciate questions and comments in this thread about whether this would be useful, what is needed, and most of all, how folks might help in small or large ways.

Thanks!

Comments

  • dave1707dave1707 Mod
    Posts: 9,725

    @RonJeffries Sounds good to me. I’d like to see something like that.

  • Posts: 2,496
    @ronjeffries - long time ago I started converting the reference section into pdf, with an appendix listing out the updates. All straight from the websites - my main reasoning was to add expanded examples to clarify command use etc. This was for my own use. Took up too much time so never completed it.

    Do you think that would be a reasonable vehicle?
  • edited November 17 Posts: 1,547

    @RonJeffries it seems like you and me and a few others have come to the same conclusion about Craft—it’s got a ton of potential and a really opaque API.

    My work with the Voxel Walker and the editor (and the texture pack fwiw) is motivated by the same awareness. It seems to me that we should have tons of cool Voxel worlds to walk around in by now. But we don’t. So I’m trying to make it easier for people to make cool things to walk around in.

    That’s why I guess I’ve been focused on an example-by-example approach, and I think there’s possibly a salient perspective that’s come of it.

    Basically, I think part of making Craft more accessible may be avoiding some of its systems altogether. Let me mention two examples, entity:add() and the touchHandler system.

    entity:add() and the associated functions seem to be a somewhat confusing system for extending the abilities of Craft components, I think. In trying to simplify the camera setup in Voxel Walker, I’ve found that I don’t seem to need them. You can add properties and functions directly to entities, and thus extend their abilities without entity:add(). It might not be possible to jettison entity:add() with all the builtin examples, but at least with the Camera examples, you can achieve all the same functionality without it, and it results in code that’s much clearer to me.

    The touchHandler system, too, I’ve come to regard suspiciously. It has some concerning pitfalls, not least of which that it seems to be completely incompatible with the default touched(touch) system. In short, as far as I can tell, using touchHandlers breaks touched(touch). It’s got some good features, to be sure, mainly that you can define how an object reacts to touches all inside its own code without having to route everything through that one touch function on the Main tab. But even so, it seems like it doesn’t add anything to Craft that can’t be done a simpler way, and it doesn’t play nice with the original Codea touch paradigm, which the vast majority of Codea projects rely on. And that makes a big hairy “gotcha” situation that newbie Craft users can get stuck in (like I did).

    Now, I’m not anywhere near familiar enough with the API to definitively assert that no one should ever use entity:add() or touchHandlers, but I think this brings up a point that deserves some thought: in addition to being confusing, some of the Craft API may be better avoided when possible.

    And if, for example, it would be better for people to ignore touchHandlers, it could actually be counter-productive to document it and create examples for it.

    I don’t have a tidy conclusion to offer but I think the matter may deserve some discussion.

  • Posts: 1,275

    yes. the doc repo @John mentions might help. but i'm finding it daunting, as today's slog of an article shows. i could learn faster by doing bigger things, but they'd be more cut paste, less real understanding.

    but that article is so boring, maybe i need to learn a lot and only then write.

    @Bri_G your pdf thing sounds good. maybe access to john's repo is better, i'll try to look at that. i suspect markdown format would be preferable to pdf, but don't know what they have now.

    it would help if all the craft example code in the docs could run. maybe there needs to be a place to plug it in, to save duplication ...

    at this point, i just don't know, still floundering.

  • edited November 17 Posts: 1,547
    Might I suggest making the problem at hand the problem at hand?

    What if the first aim of the articles was to make the very judgment call under discussion?

    Like, the whole point could be playing with the systems presented to us, so as to gauge if they’re worth documenting in the first place?
  • Posts: 1,275

    I'm sorry, I don't grasp the idea ...

  • Posts: 1,547
    Maybe it’s not a good idea then lol!

    Your articles are like explorations, at least that’s how they read to me: you have a goal, you try some things, you see how those things work, and you watch your own thought process and report on it.

    I was just wondering if, before creating an exhaustive documentation of any given Craft way of doing things, there might be a step exploring the question “is this a good way to do things in the first place?”
  • Posts: 1,547
    Attached: the project I made as my first attempt to learn Craft, way way back when it first came out: “Li’l Learn Craft.”

    I found the Learn Craft project of little help, so to better understand it I tried to make a clearer version of it.

    It’s rudimentary, but possibly worthwhile to examine in the context of the question “what do we want examples to do?”
  • JohnJohn Admin Mod
    Posts: 679

    TouchHandler and OrbitViewer are definitely not that great. They were just made for the examples and come with some annoying baggage. It's part of the issue with mixing declarative processing style code with OOP based design and is tricky to solve cleanly

    @UberGoober Can you explain what the issue is with entity:add(component)?

    All add() does is add a component to an entity and feed it events about the lifecycle of the entity. The built-in components use this system automatically. Adding a script to your entity (i.e. a class) will call update(dt) when the scene is updated. It's pretty similar to Unity's gameObject.addComponent<Type>()

    The advantage being that you can mix and match multiple re-useable component types on the same entity to avoid having big monolithic classes. You can also use :has() and :get() to check if an entity has a component or :remove() to get rid of it. Shortcuts like entity.model and entity.material handle components for you (i.e. adding a renderer component for you)

    @RonJeffries if you want me to explain how anything works, let me know

  • Posts: 1,547

    @John I want to be clear that I really like Craft. If I’m questioning the value of sweating over making certain documentation, I know it comes from my limitations as much as or more than anything else.

    Like you just said, the declarative style and OOP don’t always fit together cleanly, and tbh one of the advantages of Codea, and often part of the fun, is that you can achieve fairly complex things really easily, and that’s explicitly because you don’t have to worry about OOP. So the more OOP-style Craft paradigms sometimes make me feel a kind of torn loyalty.

    The entity:add() system seems really elegant and powerful and clear when described, and in practice I just don’t seem to be able to get my head around it. This mostly comes up around trying to understand cameras.

    I made this little “rig” system that I’ve been using for cameras because, while working on the Voxel Editor, I kept trying to manually place the ‘player’ at a certain place, and I couldn’t seem to do it. There seemed to be multiple entities involved, and then the camera and the viewers each have some kind of associated entity, maybe, I think, I’m never totally sure, and I didn’t seem to be able to get anywhere by setting a simple “position” value on any of them.

    So I just tried to make it simpler for myself. If you look at the code in my most recent Voxel Walker zip (also attached here), it should be pretty easy to see what I’ve done, because for the most part I’ve just snipped up your existing code and organized it all a very slightly different way. My overall goal, though, was to have the whole setup simple enough that if I wanted to place or rotate something I always knew exactly how to do it.

  • JohnJohn Admin Mod
    Posts: 679

    @UberGoober the rig concept you've got looks pretty interesting, it looks like you've focused on the idea of procedural setup functions, that apply a specific configuration to an entity

    If I were to do a component based version of the same thing I would make it look something like this:

    scene = craft.scene()
    
    -- Get the default scene camera and apply a first person rig
    -- This automatically gets/or adds a camera and registers for touches
    scene.camera:add(CameraRig.FirstPerson)
    

    The entity:add() method returns the thing you created so you don't have to call get after

    The code would be almost exactly the same as yours, but use a class instead of functions, i.e.

    CameraRig = class()
    -- shared camera rig functions, etc...
    
    ...
    
    CameraRig.FirstPerson = class(CameraRig)
    
    function CameraRig.FirstPerson:init(entity)
      assert(entity.hasCameraRig == false, "Entity already has a rig!")
      entity.hasCameraRig = true
    
      entity.camera = entity:get(craft.camera) or entity:add(craft.camera, 45, 0.1, 1000, false)
    
      -- etc...
    end
    
    function CameraRig.FirstPerson:update(dt)
      -- any additional stuff that needs to be updated each frame
    end
    
    ...
    
    CameraRig.FirstPerson = FirstPersonCameraRig
    
    

    I'm realising now, that there should probably be component:added() and component:removed() methods that would be called automatically when the component is added and removed. There is already a component:destroyed() which is called when the entity is destroyed. This would let you automatically clean up your component like you have with the clearRig(camEntity) method.

    I do like how you've used custom properties on entities to store extra information, which is a perfectly valid way of using them.

  • Posts: 1,547

    @John that seems like a good way to go about things, though the OOP pattern constrains rigs to being one-per-entity.

    In the Player above, I found myself creating a rig for attaching a rigidbody to an entity, which has nothing to do with cameras, and in fact to compose the Player, I’m actually piling a few rigs on top of each other, in a way that could conceptually be chained together like this:


    playerBody = cameraHybrid(scene) .joystickRig(params) .rigidCapsuleRig(params) .joystickPlayerRig(params)

    It seems to me that the main thing lost in my version of the rig pattern is the ability to remove a component from an entity, but when I think on it, I can’t recall anything I’ve come across in the examples or in my own usage where I absolutely needed the ability to remove a component.

  • JohnJohn Admin Mod
    edited November 18 Posts: 679

    Yeah, I hadn't considered your other rigs, but I feel like there is a way to combine these. Adding new methods to the entity also looks pretty interesting. Like your joystickPlayerRig jump function. My version only restricted it to one camera rig, since I don't see how you would need more than one thing controlling the camera at once

    Codea 4 should be interesting since having a Library folder that works with require should make it pretty easy to add lots of extensions, even to the point where you could just have:

    require 'uber/rigs'
    
    ...
    
    myEntity.rigs:firstPersonCamera():joystick():rigidCapsule():joystickPlayer()
    

    or something to that effect

  • Posts: 1,275

    interesting stuff above, @UberGoober and @John. I think, for now, I've just decided that a tutorial is a good idea and I'm going to try it until it isn't a good idea, or until I get tired of doing it. So ...

    1. I'll try writing some basic tutorial stuff for Craft. If I show my exploration at all, it'll be as sidebars. The articles will focus on "do this, here's what's going on" with code that I've already made do whatever it is.
    2. I plan to use the OrbitViewer because it exists. Somewhere down the road, I'll explore what it is and how it works.
    3. The camera rigs sound quite interesting, and I can imagine pulling that idea into the series at some point. Right now, I haven't explored the rigs enough to have a feeling, and freely grant that I don't quite get the general idea of what a "rig" means to you, @UberGoober.
    4. I appreciate the offer of help, @John. Are questions here the best way to ask? If not, let me know. In particular, where might I have found out about :has and :get? Is there code I should/could read as background info?
    5. Agreed on the value of added and removed methods.
    6. I heartily approve of the use of classes in this, and would urge greater consistency in doing that, and in the details of the resulting classes.

    Anyway I think I'll get down to writing the first tutorial bit and see what happens.

    Thanks, folks! Let's see if we can build up some momentum and get some more good things happening.

  • edited November 18 Posts: 1,547

    @RonJeffries I think I can explain rigs simply.

    Craft’s camera:add() is conceptually straightforward: to add features and controls to craft.camera, you don’t subclass it, you write a separate “helper” class, give it all the functions and properties it needs, and then “add” it to the camera.

    All rigs do is take an entity and add the camera, the properties, and the functions directly to it. No custom object necessary, you’re just “rigging up” a normal entity object. That’s it.

    You can literally cut-and-paste the code from a helper class into a rig; the attached example project shows rigs that do just that with the OrbitViewer and the FirstPersonViewer.

  • Posts: 1,275

    I'll have to read it, I guess. My question was more "what are rigs, in general, in your mind? what do they mean to you, how are they different from other ways, what do they bring to the table for you?"

    Here's today's article, a cut at a tutorial style.

  • edited November 18 Posts: 1,275

    @UberGoober do you know what the sensitivity, depth buffer clearing and color clearing are about? I see that you have setters and getters for them ...

  • Posts: 1,275

    This is a very early impression and I mean it to be helpful not irritating. :smile:

    The rigs, especially the orbitviewer one seem very flat. I'd hope that there would be more depth to the tree of capability, which might be easier to obtain if the rigs used classes directly rather than just functions.

    Does that make sense?

  • edited November 18 Posts: 1,275

    I think I may disagree with @John on the idea of tapping new custom properties into the existing entities. I have at least these concerns with it:

    1. There is a danger that one will clobber an existing system property;
    2. There is a danger that Joe's properties will clobber Jane's;
    3. Reading the code, it is nearly impossible to determine which properties are standard and which are local to some particular rig or other code.

    I think it would be better if Codea were to:

    1. Expose a standard place for added properties, like `entity.properties;
    2. Provide an interface that's better than `e.properties.propX = mumble, so that people still can't accidentally hit each other's properties.

    Failing that, it would be wise if folks adding properties to an entity were at least to give their properties names like uberPropX rather than just propX. But a good general rule, it seems to me, is not to extend system objects and classes.

  • edited November 18 Posts: 1,547

    I’m not sure what you mean by “flat”. They’re plain and un-fancy and use a simple system instead of a cool one, but I suspect you’d find those things virtues, as I do.

    As to what rigs mean to me, I made this system because I couldn’t figure out how to place the camera.

    With the viewers, there’s a viewer object, a camera object, and there’s also an entity in there doing something, and I think there’s even a camera.entity that comes into play at times, and plus also the relationship between a component that’s been add-ed and the object it was added to is never super clear in the first place, and then when you get to the BasicPlayer it adds a second viewer component to an entity inside itself, which might mean there are two full sets of variables for accessing viewers, entities, cameras, camera.entities, and what have you—I’m not sure because my head’s already swimming by that point.

    I’m sure if I understood the add() system better it would have been easy for me to sort that out, but I found myself struggling like heck to just get the Walker in the place I wanted and rotated to face the direction I wanted. Who do I set the position on? Who do I rotate? Do I rotate with quats, Eulers, or the strange rx, ry system introduced in the builtin viewers? I just couldn’t sort it out.

    So I said, heck with it, I need another way. I’m gonna start with something I know exactly how to place and rotate—a regular old entity—and then I’m gonna grab all this viewer code and do my best to just put the one single entity at the core of it. And then I’ll never have to struggle with how to place and rotate things again.

    And so that’s what I did.

    So fundamentally what a rig means to me is boiling the complex camera system down, as much as possible, to a plain old entity.

    Like I said, the goal was to end up with something I could easily (and confidently) place and rotate, but that came with some unexpected bonuses.

    One of which being that it’s super easy to change rigs. In the example project there’s a button that instantly switches between an OrbitViewer and a FirstPersonViewer, something I’ve struggled with, and never fully succeeded at, many times in other projects. Here it’s a function call.

    The other bonus was that making a third-person camera effect was super easy. So much so that I basically did it by accident. The lack of a third-person view has always bugged me in the Craft Voxel Terrain example, and now, thanks to making rigs, I’m moderately excited to be able to integrate one soon.

    So, you know, regarding overall design principles, I understand your objections, and I’m not sure I can say rigs are good code, but I can say that they’re obvious code. And I’m liking obvious, because I can work with it.

  • Posts: 1,547

    I like the entity.properties idea a lot, and Codea doesn’t need anything new to support it right now.

    Ironically the problem it solves is the same one that makes it possible to implement, lol.

  • Posts: 1,275

    Hm, ok, I think I get what you're trying to do. I've not worked enough with cameras to have a sense of what I'd do. I think we can be sure that I'd do it with classes, and probably more than one.

    To be obvious to a reader, I think they'd have to be more modular. In particular, the touch stuff seems to me to be able to be separated out. As it is, it seems like one big thing that could be three or four smaller things. To me, small things working together is more obvious than one big thing. YMMV.

    But, again, you've done it, and I haven't, so there;s that. On the other hand, I've read a lot of code ...

    Did you see my question about sensitivity, etc.? Any idea what those are/

  • JohnJohn Admin Mod
    Posts: 679

    @RonJeffries I think setting properties on entity can be thought of as duck typing, which would require people to agree on common interfaces at some point, and as you said is easy for clobbering to occur

    @UberGoober I think flat is meant in that there is no real class structure in the rigs concept, so it's more of a loose arrangement of parts, making it difficult to discern how it works

    The components concept (i.e. :add(), :has(), :remove() is designed to completely sidestep all these issues. You can mix and match components, have their own local storage, and can be added or removed at will to change an entity's behavior. Me liking things like jump() are, in my mind, handy shortcuts that could be exposed and mentioned in documentation. I think its best to only add a few "hero" methods and properties for commonly used things.

    You could also do something like this:

    rc = myEntity:add(RigidCapsule, params)
    
    -- if jump() method itself is added
    myEntity:jump(height)
    
    -- or if used as a custom property for the component
    myEntity.rigidCapsule:jump(height)
    
    -- or use the reference returned by add()
    rc:jump(height)
    
    -- or just get the component (less efficient)
    myEntity:get(RigidCapsule):jump(height)
    
    -- or we add some wacky thing like Unity, which calls jump on every component that has the relevant function
    
    myEntity:sendMessage("jump", height)
    
    

    Could also have other convenience things like

    -- only add if it doesn't already exist
    rc = myEntity:getOrAdd(RigidCapsule, params)
    
    -- add or replace existing component
    rc = myEntity:addOrReplace(RigidCapsule, params)
    

    Codea 4 is going to revamp the scene system, so this stuff will evolve, given that it's designed to work both in code and with an editor

  • Posts: 1,275

    @John the viewers have a method scroll that seems to be unused. Can you clue me in on what it's about?

    And where did that damping function come from? I'm curious.

    Thanks!

  • JohnJohn Admin Mod
    Posts: 679

    @RonJeffries scroll works with scroll events created by trackpad devices, such as the iPad folio keyboard, external mouse or when running on ARM Macs

    The damping function was probably something I found on StackOverlow or something similar to emulate the way Apple does their zooming (when going beyond min or max zoom levels). This is why it uses log, which starts off roughly linear and goes starts to become asymptotic

  • edited November 19 Posts: 1,275

    ty for those. here are a few more.

    what's the relationship between models and blocks? does one use the other somehow?

    is there a way to make a model cube show transparency like the glass block does?

    Is there an example of adding cubes to build up a shape? (ah, found model:addElement in blocks.)

    As I figure out what to write about, i'm finding I'd like to understand parallels like these. any help will be welcome.

  • JohnJohn Admin Mod
    Posts: 679

    Blocks and models are separate types of renderers. Blocks are treated differently because they are combined to create a single optimised mesh when rendering volumes and voxel chunks. This is why you can't just add a model directly to a block. You can do this with a dynamic block type because it implicitly creates and manages an entity on creation/destruction, which can have a model on itself. There is an example of this in Block Library:

    -- A storage block
    function chest(capacity)
        local chest = scene.voxels.blocks:new("Chest")
        -- must be dynamic to have custom models
        chest.dynamic = true
        -- The transparent geometry type doesn't cull adjacent blocks, which we need to use since this block has gaps around the edges
        chest.geometry = TRANSPARENT
    
        -- this gets called when the block is created (i.e. when generated or placed by voxel drawing code or a player)
        function chest:created()
            -- cache the block's entity
            local e = self.entity
    
            -- create some additional entities for the separate lid and base and the lid's hinge
            self.base = scene:entity()
            self.base.parent = e
            self.base.position = vec3(0.5, 0.3, 0.5)
            local r = self.base.model = craft.model.cube(vec3(0.8,0.6,0.8)))
            r.material = craft.material(asset.builtin.Materials.Specular)
            r.material.diffuse = color(133, 79, 30, 255)
    
            self.top = scene:entity()
            self.top.parent = e
            self.top.position = vec3(0.1, 0.6, 0.1)
            local r2 = self.top.model = craft.model.cube(vec3(0.8,0.2,0.8), vec3(0.4,0.1,0.4)))
            r2.material = craft.material(asset.builtin.Materials.Specular)
            r2.material.diffuse = color(66, 47, 30, 255)
            self.angle = 0
        end
    
        -- update (called every frame) is used here to keep the chest lid animation in sync with the tween created during opening/closing animations
        function chest:update()
            self.top.rotation = quat.eulerAngles(0,  0, self.angle)
        end
    
        -- custom function that can be called by player code, in this case used to open and close the chest. No actual inventory management has been created as of yet but could be
        function chest:interact()
            if not self.open then
                self.open = true
                tween(0.6, self, {angle = 90}, tween.easing.backOut)
            else
                self.open = false
                tween(0.6, self, {angle = 0}, tween.easing.cubicIn)
            end
        end
    
        return chest
    end
    

    For transparency you can use:

    local mat = myEntity.model:getMaterial(1)
    mat.blendMode = NORMAL|ADDITIVE|MULTIPLY
    mat.opacity = 0.5
    

    You can do this on any material, this will do it for the default material on a model. You can also do it directly on entity.material if the entity has one

  • edited November 19 Posts: 1,547

    I think, depending on context, extending entity is fair game.

    To me, the major strength of Codea—I’d go so far as to say the addictive part of Codea—is that it helps you do cool things fast.

    I think @dave1707 regularly (and apparently effortlessly) spits out great examples of this. I just recently had a question about rotating entities. With under 60 lines of code @dave1707 made a demonstration that not only simulates flying over a scrolling landscape but also implements touch controls for changing direction.

    That’s power. Codea power. I couldn’t do it. I hope one day I’ll be able to do it. And I think it’s good that Codea can do it. But trying to apply OOP everywhere is not gonna teach anyone how to do it.

    I realize this is regurgitating an age-old argument over OOP, and I think that’s the point; Codea’s a living breathing embodiment of the argument itself. It can leverage the best of both worlds. It can also fall into the pitfalls of both. And I think maybe the takeaway is that the best Codea examples show off both.

  • Posts: 1,547

    [[ I will pick up the rig discussion in the editor thread where it’s more germane ]]

  • Posts: 1,275

    There are many ways to do things. Quick and dirty is sometimes OK. Objects aren't necessary for clarity, though they can be one good way to provide clear code. We need clear code for our future selves; we need it for teammates; and we need it for programs that grow over time, which most substantial programs do.

    One thing that objects can provide is hierarchy. Instead of a program that goes on and on like a single long sentence, we can have the equivalent of paragraphs, sections, chapters, and so on. Objects are one way, but not the only way.

    Lua uses grouping of functions under global names, like math.sin and string.gsub. Since functions can be local, we can build the hierarchy that way. CodeaUnit does some of that.

    There isn't really any difference with objects, because class() is just a function that builds a table, and thing:foo(x) just means thing.foo(thing,x), which means thing["foo"](thing,x), no more and no less. There's just convenient notation. Their ability to have instances, all of that, is just tables, names, and functions.

    Me, I don't care how anyone codes, if I don't have to work with, or understand their code. If I do have to, I'd prefer to see them producing a suitable amount of hierarchy and modularity. In my own writing, which must be a hobby, since I don't get paid for it, I try to show what choices I make, why I make them, and what happens when I do what I do.

    As should we all. Rock on!

  • Posts: 1,547

    @John asked us to work with him on improving the official Craft documentation, and here’s an example of something almost completely undocumented: setting Voxel block textures.

    Apparently it has to start with this command:


    scene.voxels.blocks:addAssetPack(assetPackName)

    assetPackName being the string name of an asset pack in the root Codea directory. This seems to tell the Voxel system that all subsequent references to textures should be drawn from that pack. So for example this tells the Voxel system to use the built-in “Blocks” asset pack:

    scene.voxels.blocks:addAssetPack("Blocks")

    Then, to make a block use a given texture, you apparently have to first define a new type of block like this:


    scene.voxels.blocks:new(newBlockTypeName)

    …which returns some kind of type definition object for the new block type. It seems important to capture that returned type object, because I’m not aware of any other way to get it later, and we need it for setting the texture of that block type.

    So the initialization of a new block type would look something like this:

    grass = scene.voxels.blocks:new("Grass")

    And now that we have that type object, we can set its texture like this:

    grass.setTexture(ALL, "Blocks:Dirt Grass")

    A couple things to notice here:

    1. The first parameter is a global for which there is no documentation. In the lua bindings there’s this, presumably the relevant set of globals:

    // Facing Constants Lua::setGlobal(L, "NORTH", BlockFace::North); Lua::setGlobal(L, "EAST", BlockFace::East); Lua::setGlobal(L, "SOUTH", BlockFace::South); Lua::setGlobal(L, "WEST", BlockFace::West); Lua::setGlobal(L, "UP", BlockFace::Up); Lua::setGlobal(L, "DOWN", BlockFace::Down); Lua::setGlobal(L, "NONE", BlockFace::None); Lua::setGlobal(L, "ALL", BlockFace::All);
    1. The second parameter is the name of an image file, without an extension, but preceded by the asset pack name. So even though the asset pack was defined at the start of all this it seems necessary to also explicitly include its name here.

    …and having done all that, now any block that gets assigned the “Grass” type will use the defined textures.

    so…

    If we were to set about helping John add some official documentation for these functions, how would we do it?

  • I've bounced off Learn Craft several times ... would love a tutorial!

  • Posts: 1,547
    @BeeObLeo there’s a beginner’s tutorial series just started by @RonJeffries: https://ronjeffries.com/articles/-z022/codea-craft/-z02111/cocratu-001/
  • Posts: 1,275

    Thanks @UberGoober ... @BeeObLeo there's not much there yet, but there's some. Let me know here or on Twitter if you look and have issues, questions, suggestions ...

    R

Sign In or Register to comment.