It could take years to figure out all the ins and outs that go into a game engine. Even as a programmer who's tried to make my own engines, I've found it a lot more satisfying to start from an existing basis. And that doesn't make it easy, just easier. :(
I was looking at free engines I could suggest to a non-programmer friend of mine, and I liked Construct 2 a lot (https://www.scirra.com/).
Construct 2 is a tool with free and paid versions, and I tried the free version. One thing I like is that even though it has a rich editor for arranging level designs and animations, it also lets you write plugins that use arbitrary JavaScript code. This means it's probably really easy to put in a JavaScript evaluator. That said, once you're trying to invoke Construct 2's special-purpose functionality from JavaScript code, I have no idea if it's as easy to use as the editor.
Another reason I like Construct 2 is that it saves the game in a pretty readable XML format, so if it really doesn't work out, it's probably not hard to write a tool to parse what you've made and translate it to another system. Knock on wood...
These two features give me the confidence that if things get messy, someone can don a programmer hat and write a chunk of dedicated code to get things back on course.
It turns out I didn't actually get very far in the project, though. After I built a few moving platforms and teleporters, I found some of the platforming physics to be quirky in ways that it seemed like I'd want to dig into the engine to fix. While it seems like I could put in a custom implementation of platforming physics with JavaScript code, I was no longer excited enough to keep up my momentum in the project. But those quirks had to do with the details of what happens when the player gets crushed under a moving platform, and I was also trying to do frame-perfect fine-tuning of the jump physics, so if only I were in a somewhat less picky mood, I could have gotten further in the project.
It's a popular system that's been around for a while, I got the bundle just in case I want to see what it's capable of someday.
---
"4. How does one control scrolling in different directions? Do you create the screen in memory and then instruct the system to scroll in the direction of the newly created portion?"
The exact details depend on the game engine, but action games are typically written in an object-oriented style with some "stepper" logic that runs at a regular interval. In each run of the stepper, you process user inputs and determine what graphics will be rendered to the screen. Each step of the stepper may be divided into multiple phases, like a user input interpretation phase, a physics phase, a collision resolution phase, and a graphics rendering phase.
Scrolling is a common need, and there are some traditional optimizations and gameplay conventions that apply to scrolling (such as not updating objects that are too far offscreen), so game engines often offer built-in support for it. A typical interface is that you have a scrollable stage object that you put things in, and you can make a camera object and put it in the stage too. Then you make a camera.setCoordinates(x, y) call to update what part of the stage is showing. You'll tend to make this call at some point in one of the stepper phases, any time after you've calculated the player's new physics position but before the graphics have been drawn.
It's possible I'm misunderstanding what y'all mean by "maze game". Does something in text mode like https://github.com/ryanb/ruby-warrior qualify? It's probably at the bottom of a steep hill with Dwarf Fortress at the top..
If text mode is an option, I'll plug my Basic-like http://akkartik.name/post/mu language. My students have made tic-tac-toe and a card game with it. Maybe we should try a maze game next. Here's a text-mode chessboard program, for example: http://akkartik.github.io/mu/html/chessboard.mu.html. With tests for screen and keyboard access (search for 'scenario'). I'm sure it looks like Greek, but take my word for it that 11- and 12-year olds found it pretty easy to work with. Happy to show more over a Hangout or something.
Ack, right after I typed all this out I remembered the Windows constraint. That disqualifies Mu, at least immediately. I knew there was a reason I chose to keep mum when I saw jsgrahamus's post last night.
I know someone who's making a game with Clickteam Fusion and considers it almost a form of betrayal to go with GameMaker. :-p So when I brought up GameMaker, I was thinking of mentioning Clickteam Fusion as well, but the usual cost is $99. I don't really know enough about it to know that it's worth that price tag. Now that it's on sale, I guess I'll be picking it up just in case I want to use it sometime, just like the other one.
I've been noticing continuities between social code distribution, modularity, and variable scope. A guiding example is code verification:
Unrecorded reasoning, existing mainly in our minds.
-->
Codebases dedicated to proofs or tests.
-->
Proofs or tests located in the codebase they apply to.
-->
A type/contract declaring a module interface.
-->
A type/contract annotation for a function definition.
-->
A type/contract annotation for an individual expression.
-->
A type/contract annotation for an individual built-in operator, but at
this point it becomes implicit in the operator itself, and we just
have structured programming, enjoying properties by construction.
Verification is a simplified version of a build process; it's just a build with a yes or no answer. So the design of a build system has similar continuity:
Unrecorded how-to knowledge, existing mainly in our minds.
-->
Codebases or how-to guides dedicated to curated builds (e.g. distros).
-->
Build scripts and docs located in the codebase they apply to.
-->
Macroexpansion-time glue code, importing compiler extensions by name.
-->
Load-time glue code, importing runtime extensions by name.
-->
Service-startup-time glue code, obtaining dependency-injected fields
by name.
-->
An expression, taking free variables from its lexical scope by name.
(This is a build at "evaluation of this particular expression" time.)
There might be some rough parts in here. I might be taking things for granted that I don't want to, like taking for granted that we want unambiguous named references from one module to another. My point with this continuity is to note that if I don't want named imports, then maybe I don't want named local variables either; maybe tweaks to one design should apply to the other.
And this means that even local syntactic concerns extrapolate to social decisions about how we expect to deal with our unrecorded knowledge. Every design decision has a lot to go by. :)
---
Another exciting part is that I think nested quasiquotation shows us a more general theory of lexical locality. If we're dealing with syntax as text, then locations in that text have an order, and we can isolate code snippets at intervals along that order (and mark them with parentheses). Intervals are partially ordered by containment, so we can isolate code snippets at meta-intervals between an outer interval and multiple nonoverlapping inner intervals (and mark them with parentheses with nonoverlapping parentheses-shaped holes: quasiquotations).
That "nonoverlapping" part seems awkward, but I think there's a simple concept somewhere in here.
With this concept of intervals, I'm considering higher degrees of lexical structure past quasiquotation, and I'm considering what kind of parentheses or quasiquotations would exist for non-textual syntaxes.
A module system deals with a non-textual syntax: The syntax of a bundle of modules. If the modules have no order to them, then we don't even have parentheses to work with, let alone quasiquotation. But they can have an order to them. We can impose one from outside:
Module A precedes module B.
And anything we can impose from outside, we might want to add as a module:
Module A says, "..."
Module B says, "..."
Module C says, "Module A precedes module B."
This is prone to contradictions and ambiguities. If we can say how to resolve these ambiguities from the outside, we should be able to do so as a module:
Module A says, "..."
Module B says, "..."
Module C says, "Module A precedes module B."
Module D says, "Module B precedes module A."
Module E says, "If module C and module D disagree, listen to module C."
Module F says, "If module C and module D disagree, listen to module D."
Module G says, "If module E and module F disagree, listen to module E."
This should lead to a very complete system of closed-system extensibility: For any given set of modules, if the set's self-proclaimed ordering between A and B is currently unambiguous, then we might as well listen to it! If we don't like it, we can add more contradictions and disambiguations until we do, right up to and including "Ignore all those other modules and do it like this." :)
With this ability to disambiguate when things go wrong, we can model lexical scope:
Module A says, "Export foo = (import bar from system {B, C})."
Module B says, "Export foo = 2."
Module C says, "Export bar = foo + foo."
Result: foo = 4.
While both A and B have an export named "foo," this conflict is disambiguated by the fact that module A is treating {B, C} as a local scope. I intend this to mean that bar isn't at the top level either.
If we really want access to bar at the top level, we can refer to it again, and we can even be sloppy about it and make up for our sloppiness with disambiguations:
Module A says, "Export foo = (import bar from system {B, C})."
Module B says, "Export foo = 2."
Module C says, "Export bar = foo + foo."
Module D says, "Export all imports from system {B, C}."
Module E says, "If A and D export the same variable, listen to A."
Result: foo = 4; bar = 4.
If we want, we can have the top-level bar see the version of foo exported by A, even though the version of bar used by A still uses the foo from B:
Module A says, "Export foo = (import bar from system {B, C})."
Module B says, "Export foo = 2."
Module C says, "Export bar = foo + foo."
Module D says, "Export all imports from system {A, B, C}."
Module E says, "Export all imports from system {C, F}."
Module F says, "Export foo = (import foo from system {A, B, C})."
Module G says, "If D and E export the same variable, listen to D."
Result: foo = 4; bar = 8.
Not easy enough to extend? Define some structure. Write modules that assign folksonomic tags to other modules or themselves, and then refer to the system of all modules with a given tag. Write modules that act as parentheses, and write modules that determine enough of an order to decide which modules those parentheses contain. Here's an example of the latter:
Module A says, "Export foo = (import bar from range R1)."
Module B says, "Export interval R1, and begin it here."
Module C says, "Export foo = 2."
Module D says, "Export bar = foo + foo."
Module E says, "End interval."
Module F says, "These modules are in order: B, C, D, E."
The flexibility is obviously really open-ended here, and it's going to be a challenge to make this a well-defined idea. :-p
Quality of a name is relative to a purpose. The more public we go, the more meanings compete for a single name, making us resort to jargon. If a language really only uses homogenous intensional equality, being able to call it = is a relief. If someone wants to build a side-by-side comparison of several versions of an extension, they might prefer for some of the names to be different in every version while others stay the same.
But it's not just names per se. In that side-by-side comparison, they might also want to merge and branch parts of the code whose assumed invariants have now changed; invariants can act as Schelling points, like invisible names. Modifying code is something we do sometimes, and I think akkartik wants to see how much simplicity we'll get if everyone who wants a simpler system has the tooling support to modify the code and make it simpler themselves.
Personally, I find it fascinating how to design a language for multiple people to edit the code at the same time, a use case that can singlehandedly justify information hiding, modules, and versioning. But I think existing module systems enforce information hiding even more than they have to, so that in the cases where people do need to invade that hidden information, they face unnecessary difficulties. I think a good module system will support akkartik's way of pursuing simplicity.
But... my module system ideas aren't finished. At a high level:
- You can invade implementation details you already know. You can prove this by having their entire code as a first-class value with the expected hash.
- You can invade implementation details if you can authorize yourself as their author.
"And this seems like a waste of the only paired characters Arc doesn't use."
I don't think even the [] syntax really pulls its weight. Between (foo [bar baz _]) and (foo:fn_:bar baz _), the latter is already more convenient in some ways, and one advantage is that we can define variations of (fn_ ...) under different names without feeling like these variations are second-class.
(Arc calls it "make-br-fn" rather than "fn_", but I wanted the example to look good. :-p )
Personally, I've never gotten comfortable with the colon intrasymbol syntax. Even in your example, I'm having trouble parsing it right now. I really don't like how it makes "bar" look like part of the first part, not the second.
"I tried from the installation folder, and it works. The error appears only when I try to call it from a different folder."
That's good to hear. Thanks for sharing your process.
It would be useful to find out what's causing that error in particular, so I'll check on that in a moment.
(This is something that the official releases of Arc were pretty picky about; the language would load paths relative to the current directory, so you pretty much had to start Arc from the same directory as strings.arc, etc. I think Anarki has occasionally made changes to this behavior, but maybe Arc/Nu is still picky.)
Hmm... It looks like it's my turn to have trouble! What version of Racket are you using? I might need to revert to that version to get Arc/Nu working on my system. XD
---
"Then I decided to try to run the news from a directory(not the installation one). So i put all the library files in that directory [...]"
Now that I've read this again, that sounds like something that would break the libraries in some way. The files of Arc/Nu contain relative paths to each other. If you move some files, you might need to edit the paths that occur in the other files. Which files were you trying to move where?
---
For anyone here who can help, here's the problem I'm encountering.
When I try to start Arc/Nu on Windows 10 with Racket 6.4 or 6.5 installed, I get this error:
I've tracked it down to a specific place: The execution of the Racket code (mac % args (% args)) in lang/arc/3.1/main.
That line is trying to set a hash table entry in Arc/Nu's (globals) table. In the file "compiler", when (namespace-require path) is called to load lang/arc/3.1/main dynamically, the (globals) parameter has been parameterized to contain an appropriate table to store that definition.
However, when lang/arc/3.1/main requires the "compiler" module, it loads a second instance of the module. So when it tries to access (globals), it gets the original value: (define globals (make-parameter #f)). Since #f isn't a hash table, hash-ref raises an exception.
Does anyone know if there's a way to make lang/arc/3.1/main reuse the same "compiler" module instance that's already been loaded?
That sounds super strange. From what I understand, Racket automatically caches modules, so they are only loaded once.
But then again, Racket does have multiple phases, so it's possible that it's loading the "compiler" file in two different phases, causing duplicate variable definitions.
In addition, Racket doesn't allow for mutually recursive modules, but Arc/Nu is using "namespace-require", which apparently bypasses that restriction. It's possible that there is a race condition that is causing the "arc/3.1/main" file to be loaded before the "compiler" file is finished loading, which then triggers a second load of the "compiler" file.
Just to clarify: have you tried inserting a "displayln" in the "compiler" file to verify that it is being loaded twice?
Actually I was wrong in saying that in the installation folder it worked. In the installation folder it succeeded in loading the "news.arc" file, and failed at runtime. While in the other folder that failed, because in that folder I already tried with anarki and created some news/bans and this is why it failed to load: the diskvar didn't work. However the problem wasn't in diskvar: it was in "file-exists" which should return the name of the file in case of true: because of this when I just modified "diskvar" it still gave me the contract violation error, I think because the arc code relies on the fact that file-exists returns true. I changed in "lang/arc/3.1/main" the line "(def file-exists (x) (tnil (file-exists? x)))" in "(def file-exists (x) (if (file-exists? x) x nil))". Now I successfully run the news!
Whenever you see an error message complain about an undefined identifier named "_R", that's because of a long-standing issue with the Racket reader in Windows terminals. If you paste multiple lines into the terminal, what you paste needs to have a blank line at the end already or else Racket will think it sees an R somewhere in the middle of your code.
It's not your fault. It's a bummer to have to work around this.
If you just want to define a function in one place, this is pretty idiomatic:
(def reverse (x)
(if alist.x
rev.x
(case type.x
string (string:rev:coerce x 'cons)
(err:+ "Called reverse with " (tostring write.x)))))
If you want to write the different cases in different places in the code, here's the way Anarki (https://github.com/arclanguage/anarki) currently defines `rev`:
; in arc.arc
(def rev (xs)
"Returns a list containing the elements of 'xs' back to front."
(loop (xs xs acc nil)
(if (no xs)
acc
(recur cdr.xs
(cons car.xs acc)))))
; in lib/strings.arc
(defextend rev (x) (isa x 'string)
(as string (rev:as cons x)))
The community has made several little definition utilities for doing predicate dispatch in Arc, and `defextend` is a good example of those, so it's pretty idiomatic.
There's an unquote inside another unquote there, so it's actually trying to do a function call (unquote name), which doesn't work because `unquote` is undefined. There's also the problem that your (with ...) body is sitting outside the (with ...) form.