Nepeo's Forum Posts

  • I don't recommend interfacing with the cordova plugin directly. The API only supports one sign in state callback at a time, so if you override the callback then the plugin interface will stop receiving sign in events. So you basically have to replace everything the construct plugin does. Also worth noting that you will not be able to interface with cordova plugins when in worker mode, as they have to run in the window context.

    I would advise placing some functions that deal with the state changes in your script file, then calling those functions from the eventsheet when the condition is met.

  • iggyplusceci we're happy to investigate issues, but we need to know the scenario. Without that we're in the dark about whatever the problem is.

    We test on multiple devices, of differing ages and Android versions. But issues are often related to specific projects, devices, plugins or combinations of plugins. It's pretty much impossible to test every situation, so we cover the common ones and rely on user bug reports and the beta cycle to root out the rest.

    If you have a specific problem you are aware of please file a bug report, and we will investigate it.

  • Fengist that Boid model sounds pretty interesting, thanks for posting it!

  • Try Construct 3

    Develop games in your browser. Powerful, performant & highly capable.

    Try Now Construct 3 users don't see these ads
  • I've confirmed that a debug build of my Mobile Advert test project created in r157 installs correctly on a device.

  • TechBoxNorth I can't say I've experienced this problem with debug APKs. But I have seen it previously when installing signed release builds locally. I assumed this was because I was installing release builds from a source other than Google Play.

    If Play protect decides an app is something "suspicious" then it will show you a dialog during installation. If you pick the wrong option on the dialog your app ends up on a blacklist on the device for awhile, so installing will silently fail after that. The default option is to block the app.

    There is also a global blacklist for applications which will block installation on any device, you have to appeal to Google to get this revoked.

    If this to a change we have made it is not one we know of. There have been a number of modifications for that release, including a fairly major breaking change to the Mobile Advert plugin which we have talked about here.

    It's difficult to tell if the joinner was having exactly the same issue, it sounds to not be related to Google Play Protect.

    iggyplusceci your problem sounds completely unrelated to this. If you think there is a bug and have a replication then feel free to file an issue on our bug tracker. Mentioning a problem in passing on the forum is unlikely to get the attention of the construct team, and it's not enough for us to actually investigate a problem. I'm inclined to say that the Amazon QA team would not have rejected your application for being slow to start. There's a few reasons I can think of which could explain why the game did not load for them, but it's purely speculation.

  • In JS land you would use a module loader to deal with this normally, the files are already loaded so you don't really need to worry about waiting for the file to load. You just need the blocks to execute in the correct order.

    const moduleInitalisers = new Map;
    const moduleDirectory = new Map;
    
    function define(moduleID, dependancies, moduleInitialiser) {
    	if (moduleInitalisers.has(moduleID))
     		throw new Error(`"${moduleID}" has already been defined`);
    	moduleInitalisers.set(moduleID, [dependancies, moduleInitialiser]);
    }
    
    function require(moduleID) {
     	function getModule(moduleID, parents) {
     		if (moduleDirectory.has(moduleID))
     			return moduleDirectory.get(moduleID);
    
     		const isCircular = parents.includes(moduleID);
    
     		parents.push(moduleID);
     
     		if (isCircular)
     			throw new Error(`Circular dependancy ${parents.join(" -> ")}`);
     
     		const [dependancies, initialiser] = moduleInitalisers.get(moduleID);
     		const mod = initialiser(...dependancies.map(m => getModule(m, parents.slice(0))));
     
     		moduleDirectory.set(moduleID, mod);
    
     		return mod;
     	}
     	return getModule(moduleID, []);
    }
    

    This is a very simple module loader. For each module you have you call "define" with a unique module name, a list of modules it depends on, and an initialiser function that returns the module.

    To load a module you call "require" with the module name. It will load all the modules that it depends on, then calls the initialiser with those modules as arguments. The module is then returned.

    Example use:

    define("Lemon", ["Fruit"], Fruit => {
     	return class Lemon extends Fruit {};
    })
    
    define("Strawberry", ["Fruit"], Fruit => {
    	return class Strawberry extends Fruit {};
    })
    
    define("Peach", ["Fruit"], Fruit => {
     	return class Peach extends Fruit {};
    })
    
    define("Fruit", [], () => {
     	return class Fruit {};
    })
    
    define("FruitSalad", ["Lemon", "Strawberry", "Peach"], (Lemon, Strawberry, Peach) => {
     	return class FruitSalad {
     		constructor () {
     			this.contains = [
     				new Lemon,
     				new Strawberry,
     				new Peach
     			];
     		}
     	}
    })
    
    const FruitSalad = require("FruitSalad");
    
    const mysalad = new FruitSalad;
    

    There's a few minor restrictions you need to be aware of though. The module loader itself has to be loaded BEFORE anything else, the require call has to be AFTER all the needed modules have been defined and you cannot do circular dependencies.

    The initialiser for a module is only ever called once, so this probably works best if you define the loader and the modules in script files. Then "require" the modules from inline script blocks as needed.

    If you need it then it is possible to write module loaders that support circular dependencies but they add additional restrictions that are somewhat awkward.

  • Comes under the "all sources, one destination" category apparently. Usefully this article specifically covers this technique for making a tower defence game. It's obviously not using Construct, but he explains the concept as opposed to showing code so it should doable.

    You want to produce a "vector field" for your ants to follow.

  • The video seems to suggest they are creating a flow graph for the space. Each grid cell contains a vector saying what direction an ant needs to go to get to the food. The graph only needs to be updated if the space changes. Per tick each ant looks up the grid cell it's in, and moves in the direction the cell contains. If they have food they go in the opposite direction.

    I've done some stuff in the past similar to this for particle simulation, it's relatively easy. The hard bit is generating the flow graph. I expect red blob games has an article on this technique though.

  • Mikal Testing performance is probably a good idea, but be careful your not just looking at a component in isolation. It's better to profile an actual game and see where your most expensive areas are. Making things faster is great, but if you halve the time spent doing something that uses 1% of your CPU time in an area that isn't time sensitive then it's a bit of a waste of effort.

    In terms of understanding it's actual perf. of the JSON plugin there's lots of small optimisations, but you can fall onto the slow path by doing some unexpected things. Setting the current path doesn't affect a huge amount in terms of speed, but it is stored in it's parsed form so that section of the path doesn't need reparsing. Might make some situations faster I guess.

    Using the JSON ForEach condition is faster than accessing each element as it only has to resolve one value ( the array/object ) instead of each child. When parsing paths it will cache the last parent object, the parent being everything other than the last key in the path:

    path = "a.b.c.d"
    parent = [ "a", "b", "c" ]
    child = "d"
    

    So if your only changing the child part of the path it's faster. When accessing arrays with push/pop the full path is cached instead, so the array only needs to be resolved once.

  • Mmmm I'd call it an unexpected side effect of the grouping system. It's supposed to be for files that have the same content, but different formats ( like audio in different encodings ).

    As it's putting non-script files into the script folder I'd say it wasn't correct. File an issue for it, we should probably fix that.

  • I don't think we have a method of specifically loading a CSS file yet, although it's probably something that could be done quite easily through the asset manager. Given that it's a less common requirement I don't think we will add a folder for stylesheets like we have for scripts that loads the files automatically ( unless lots of people want it ).

    There is a method for getting a URL from the asset manager ( the URL changes depending on the platform so it's important to use the asset manager here ) with that you can import the stylesheet using the DOM API's. Like so

    async function loadCSSFile (runtime, localURL) {
    	const url = await runtime.assets.getProjectFileUrl(localURL);
    	const element = document.createElement('link');
    	element.rel = "stylesheet";
    	element.type = "text/css";
    	element.href = url;
    	document.head.appendChild(element);
    }
    
    loadCSSFile(runtime, "style.css");
    
  • Mikal raised a good point about creating data with the JSON plugin. JSON allows you to create any combination of arrays and dictionaries you like, so for complex data it's much more versatile than the dictionary and array plugins.

    Under some circumstances I would expect the array and dictionary plugins to be faster, as the "path" abstraction we use for accessing JSON data in the plugin adds some overhead. Whether this speed difference matters is down to your circumstances. Your likely to speed up your development process by having more descriptive data structures, so it's a bit of a trade off.

    You can also use combination with the array and dictionary plugins, which can help in some situations. In the procedural terrain generation demo we store the "chunk" data in a JSON object, then convert it to a string and place that into a dictionary. This makes the interactions with the JSON data object simpler, and means we can quickly check if the chunk exists in the dictionary.

  • The "drawing canvas" plugin is implemented in "WebGL". Whereas Chart.js uses the "canvas 2D" API. Both WebGL and canvas 2D utilise the HTML Canvas element, but in different modes. You cannot use more than 1 mode with a canvas, or swap between them.

    As such the best way to use Chart.js would be to create a second canvas for your chart and float it over your game. There are several core plugins ( button, iframe ) that use this technique with HTML elements.

    The example that Chart.js gives has the canvas within the HTML file, and then uses JS to get a reference to the canvas. Instead of this you can create a new canvas element with JS and insert it into the document programmatically. I've adjusted the example to demonstrate how you can do this.

    // create the html <canvas> element
    var canvas = document.createElement('canvas');
    // set the size of the <canvas> element
    canvas.width = 400;
    canvas.height = 400;
    // insert the <canvas> element into the <body> element
    document.body.appendChild(canvas);
    // set the canvas to "2D" mode and get the rendering context from it
    var ctx = canvas.getContext('2d');
    // create the chart using the rendering context
    var myChart = new Chart(ctx, {
     type: 'bar',
     data: {
     labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
     datasets: [{
     label: '# of Votes',
     data: [12, 19, 3, 5, 2, 3],
     backgroundColor: [
     'rgba(255, 99, 132, 0.2)',
     'rgba(54, 162, 235, 0.2)',
     'rgba(255, 206, 86, 0.2)',
     'rgba(75, 192, 192, 0.2)',
     'rgba(153, 102, 255, 0.2)',
     'rgba(255, 159, 64, 0.2)'
     ],
     borderColor: [
     'rgba(255, 99, 132, 1)',
     'rgba(54, 162, 235, 1)',
     'rgba(255, 206, 86, 1)',
     'rgba(75, 192, 192, 1)',
     'rgba(153, 102, 255, 1)',
     'rgba(255, 159, 64, 1)'
     ],
     borderWidth: 1
     }]
     },
     options: {
     scales: {
     yAxes: [{
     ticks: {
     beginAtZero: true
     }
     }]
     }
     }
    });
    
  • Unfortunately the consent SDK doesn't actually store if the "ad free" option has been chosen. While we could pass through through the response immediately after the user has chosen "ad free" we can't pass through that value on subsequent runs. Additionally there is a slight frustration that you cannot display the dialog outside of the EEA, I filed a request with the Admob team quite some time ago about this but they haven't responded. So if you wanted to offer an "ad free" option outside the EEA you would need a separate flow. Which is frustrating, the option is mostly exposed because the SDK offers it...

    But if you did want to use it I would say the best route would be to initially check if the user has paid for ad free, or whatever restriction you've decided on. If they haven't check what the user personalisation state is. This should be loaded before the runtime starts, so the only ways it could be "UNKNOWN" are if the user consent hasn't ever been shown or if Ad free was selected. So show the user consent dialog, once it's completed the "Configuration complete" condition will trigger. If the value is still "UNKNOWN" then they chose the "ad free" option.

  • The option basically allows for a "pay for no ads" model in your app. When the user consent dialog is shown ( European Economic Area only ) it will contain the "ad free" option if you have enabled it. When selected by the user it sets the user personalisation state to "UNKNOWN" which prevents adverts from being shown.

    It's up to you as a developer if you want to offer an ad free option and what the restrictions are.

    EDIT: the documentation could probably do with updating... I'll put it on the TODO list.