FictionalAroma's Forum Posts

  • 14 posts
  • Yeah it SHOULD do, so it wont test EVERY character, only if its a space and if you are word wrapping

    Its probably a personal preference, I would keep track of more states like Current Line and Line Length than calculating as you go

    But as I say, overall, does the job!

  • Overall looking pretty good!

    That does seem to be the best way of handling a typewriter effect if you need to track each letter

    The biggest boost to performance I can see here would be to when you start the typing, parse the string for the whitespace, split out into the word char arrays, then you don't need to test every single character typed to see if you are gonna word break, or find the edge of the word each time!

  • First up, yes it is!

    So the Behaviours are part of every instance object, so are fairly easy to access

    The overall methods are documented here - construct.net/en/make-games/manuals/construct-3/scripting/scripting-reference/object-interfaces/iinstance

    So an easy example would be

    let myObj = runtime.objects.Player.getFirstInstance();
    
    let platb = myObj.behaviors.Platform;
    
  • Try Construct 3

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

    Try Now Construct 3 users don't see these ads
  • Hey all,

    While doing my first big typescript project I've posted a couple of times about random issues I have found

    I thought this time I would give back with some useful functions that I have found/written along the way!

    Most of these were found during the conversion from raw Javascript to Typescript, as by default TS doesn't let you do the weird-hacks that JS does

    Since when has that stopped anyone however!

    I will add to this thread when I find more weird things to do, the project I have building for this can be found on Github Here if anyone is interested!

    Please enjoy, and run at your own risk!

    Get Construct Object Type from String

    Example Usage => Get the specific Bullet you want your Turret to spawn, when the turret baseline are Family based

    // general typescript function, to get the property off the object using 
    export function getProperty<T, K extends keyof T>(o: T, propertyName: K): T[K] {
     return o[propertyName]; // o[propertyName] is of type T[K]
    }
    
    // get an ObjectType from the runtime objects list based on the name of the object
    // mostly used to be able to spawn items via configuration
    export function getObjectType<T extends IWorldInstance>(runtime: IRuntime, name: string): IObjectType<T> {
     // get the object key using the Construct Object Name
     // eg - runtime.objects.MyEnemyType
     const typekey = name as keyof IConstructProjectObjects;
     return getProperty(runtime.objects, typekey) as IObjectType<T>;
    }

    Get Effect Type - Bug Workaround

    Currently accessing Effects via typescript is bugged - discussed here

    // bugfix workaround for effects
    // casts the effects object to an array, then pulls the type via name
    export function getEffectType(effectArray: any , name: string): IEffectInstance | null {
     const hackCast = effectArray as IEffectInstance[];
     const found = hackCast.find((ef) => ef.name == name);
     return found != null ? found as IEffectInstance : null;
    }
    

    Parse a Project JSON File at runtime

    export const safeJsonParse = <T>(str: string) => {
     try {
     const jsonValue: T = JSON.parse(str);
    
     return jsonValue;
     } catch {
     return undefined;
     }
    };
    export const safeGetConstructFileJson = <T>(runtime: IRuntime, fileName: string) : Promise<T> =>{
    	return new Promise((resolve, reject) =>
     runtime.assets
     // get project file URL Promise
     .getProjectFileUrl(fileName)
     .then((filepath) =>
     // ajax get the file contents (also promise)
     fetch(filepath)
     .then((response) =>
     // get the text from the file (AGAIN another promis)
     response.text()
     .then((rawJSON) => {
     // parse the results
     const result = safeJsonParse<T>(rawJSON);
     result !== undefined
     // funally resolve the promise stack
     ? resolve(result)
     : reject(`Unable to parse to ${fileName} : + ${rawJSON}`);
     })
     .catch((reason) => reject(`Unable to fetch ${fileName} Datafile - ${reason}`))
     )
     .catch((reason) => `Unable to FIND ${fileName} data file - ${reason}`)
     )
     .catch((reason) => `the install didnt work, cant find ${fileName} - ${reason}`)
     );
    }
    
    

    Easy Instance Object Configuration

    runtime.objects.Type.setInstanceClass

    Doing that manually is very boring and very prone to human error

    So I made a method to do it easy

    async function ConfigureCustomTypes(runtime:IRuntime)
    {
    	const customTypeMapping = [
     {
     InstanceType: EnemyControl,
     ConstructObjectTypes: [runtime.objects.BasicEnemy, runtime.objects.BasicEnemy2],
     },
     {
     InstanceType: TowerController,
     ConstructObjectTypes: [runtime.objects.basicTower, runtime.objects.BasicTower2],
     },
    	...
    ];
    	customTypeMapping.forEach(tm => {
    		const type = tm.InstanceType;
    		tm.ConstructObjectTypes.forEach((n) => {
     if (n != null) n.setInstanceClass(type);
     });
    	})
    
    

    Make 2D Array

    Want to make a giant 2d array to represent say a grid?

    Say to map objects on top of a tile map?

    function makeArray<T>(w: number, h: number, val: T) {
     var arr: T[][] = [];
     for(let i = 0; i < w; i++) {
     arr[i] = [];
     for(let j = 0; j < h; j++) {
     arr[i][j] = val;
     }
     }
     return arr;
    }

    Get Tilemap Coords from Mouseclick

    You have a tilemap

    you have things ONTOP of the tile map, and you need to figure out if there is something IN the tilemap

    	// do this earlier somewhere to use
     mouseXYAr = this.gridLayer!.cssPxToLayer(pointerEvent.clientX, pointerEvent.clientY);
    
    
     private getGridCoordsAndSize(tilemap: ITilemapInstance, mouseXYAr: number[]) {
     const tileSizeX = tilemap.tileWidth;
     const tileSizeY = tilemap.tileHeight;
    
     const cellCoords = [Math.floor(mouseXYAr[0] / tileSizeX), Math.floor(mouseXYAr[1] / tileSizeY)];
     return { cellCoords, tileSizeX, tileSizeY };
     }
    
  • Hey again Ashley

    I thought that might be the case, thanks for letting me know!

  • Hey folks, back once again with a bit of typescript weirdness!

    This time it is around effects and how to access them in typescript

    First the obvious -

    Please find a zip of the issue recreated in the example project to assist

    GhostShooter

    Yes I have regenerated the Typescript Definitions on the project

    Yes I am ensuring the Typescript is built and the project reimports the scripts on build

    Setup

    I have added the following method to the monster.ts file, and am calling it when the HP gets to approx half.

     ChangeColour1() {
     this.effects.AdjustHSL.isActive = true;
     }
    

    The Error

    Shown below is the stack trace that occurs when this method is executed, and the debug of the line in question

    The debug shows us that correctly, yes the named property 'AdjustHSL' does not exist. Of course attempting to access directly via [] does not compile, as shown in ChangeColour2() in the example

    The Current Hack

    So I have got a 'solution', but definitely feels like a hack!

    Essentially casting the Instance effects Object to an array, then finding by name

    export function getEffectType(effectArray: any , name: string): IEffectInstance | null {
     const hackCast = effectArray as IEffectInstance[];
     const found = hackCast.find((ef) => ef.name == name);
     return found != null ? found as IEffectInstance : null;
    }
  • Well today I learned TS can just dynamic cast down the inheritance tree!

    I'm mainly dotnet and that really doesn't like you doing that so didn't occur!

    Just Cast It works!

    Thankyou very much!

  • Heya, apologies for the delay!

    So I have taken the Ghost Shooter Typescript demo and added in samples to show the example of what I was finding

    Ghost Demo Updated

    The summary of the changes are -

    Monster.TS => Added a OnCreated method to use (it just sets a property, what it does isn't relevant here)

    Main.ts => added the 3 flavours of functions

    runOnStartup => Added bindings for "instancecreate" on the Monster object type

    The reason everything went fine in the github main project was because I used <any> on the typing of the callback event.

    However this overall just felt hacky and was trying to find the correct method

    Thanks again for the help!

  • Damnit I thought it was already!

    It is public now, and I'll throw up a better example when I'm able

    Sorry for being a pain!

  • It's difficult to help without seeing all your code, but one mistake I can spot right away is where you have class EnemyControl extends ISpriteInstance, it's incorrect to extend from ISpriteInstance - you should extend from its InstanceType class, e.g. InstanceType.Enemy.

    Sorry yes, something that I now realise wasn't clear was that those code snips are the older JS code, which was meant to show what I was converting from

    So updating that particular code snip to the Angular version

    Enemy Control

    export default class EnemyControl extends InstanceType.Enemy {
     OnArrive: EnemyControlEvent | undefined;
     OnKilled: EnemyControlEvent | undefined;
     constructor() {
     super();
     this.behaviors.MoveTo.addEventListener("arrived", this.onMoveToArrive);
     }
    
     onMoveToArrive = (e: BehaviorInstanceEvent<this, IMoveToBehaviorInstance<this>>) => {
     
     if (e.behaviorInstance.getWaypointCount() <= 0) {
     this.OnArrive!(this);
     this.destroy();
     }
     };
    
     setup(levelControl: TowerLevelControl) {
     this.OnArrive = levelControl.OnEnemyArrive;
     this.OnKilled = levelControl.OnEnemyKilled;
     }
    ...
    }
    

    Level Control

     SetupLevel = () => {
     this.runtime.objects.Enemy.addEventListener("instancecreate", this.SetupEnemy);
    ...
    }
    
    Teardown = () => {
     this.runtime.objects.Enemy.removeEventListener("instancecreate", this.SetupEnemy);
    ...
     };
    
     SetupEnemy = (e: ObjectClassInstanceCreateEvent<EnemyControl>) => e.instance.setup(this);
    

    with the output error of -

    Argument of type '(e: ObjectClassInstanceCreateEvent<EnemyControl>) => void' is not assignable to parameter of type '(ev: ObjectClassInstanceCreateEvent<Enemy>) => any'.

    Types of parameters 'e' and 'ev' are incompatible.

    Type 'ObjectClassInstanceCreateEvent<Enemy>' is not assignable to type 'ObjectClassInstanceCreateEvent<EnemyControl>'.ts(2345)

    Full code is available here - https://github.com/FictionalAroma/CyberTower/tree/typescript-convert

  • Howdy Folks,

    First of all, VERY happy to have typescript running instead of vanilla JavaScript!

    I spent today converting my existing project and I was able to convert most of it, with one exception!

    The Main Question

    In typescript, how can you add an EventListener binding and utilise the registered SubClass of your type?

    The Setup

    In my version, I am combining event sheets and scripts. Might as well let the power of construct just do its thing!

    So I have a timer on the Sheet, and that spawns the enemies, nice and simple.

    My Level Controller class is setup and destroyed in and out of the level layouts, and in that we bind and unbind the instancecreate event, shown below. The EnemyControl script is the InstanceClass for all items under the Enemy family, and thus happily resolves the method call in the event callback (or explodes if i forgot to register the type, but oh well).

    This is of course all managed through the sheer dark magic of javascript typeless making the objects function

    Level Control Class

    {
     SetupLevel = () => {
     this.runtime.objects.Enemy.addEventListener("instancecreate", this.SetupEnemy);
     }
    
     Teardown = () => {
     this.runtime.objects.Enemy.removeEventListener("instancecreate", this.SetupEnemy);
     };
    
     SetupEnemy = (e) => e.Setup(this);
    }
    

    The Enemy Subclass

    export default class EnemyControl extends ISpriteInstance
    {
     constructor(){...}
     
     setup(levelControl){
     this.OnArrive = levelControl.OnEnemyArrive;
     this.OnKilled = levelControl.OnEnemyKilled;
     }
    }
    

    The Problem

    And now to Typescript, and the same event handler code looks something like

    	 SetupEnemy = (e: ObjectClassInstanceCreateEvent<InstanceType.Enemy>) => e.instance.setup(this);
    

    However, the PROBLEM is that of course the Enemy Instance Type does NOT have the extended functionality of the EnemyControl subclass, and so fails to compile

    Changing the event to be the <EnemyControl> type of course makes the callback binding fail because it doesn't match the signature required

    SO I am somewhat stuck

    Brainstorming Solutions

    I have only come up with about two options

    1. Move my Tower Controller class into a Global access scope and then just utilise it in the constructor of EnemyControl
    2. change the <T> on the event to be any - SetupEnemy = (e: ObjectClassInstanceCreateEvent<any>) => e.instance.setup(this); This at least does not appear to give me errors on compile!

    I am fairly certain that the <any> will work, as really that falls back to older javascript processing, I just cant say I like it. But I hate it less than the globaling.....

    Any ideas or things I missed please let me know!

  • Hmm, maybe I'm understanding something wrong but wouldn't this work? (Pseudocode)

    > 	if X tapped -> { doThing(X); return;}
    	if Y tapped -> { doThing(Y); return;}
    	if Z tapped -> { doThing(Z); return;}
    
    	// nothing was tapped
    	doThing(Bail);
    	return;
    

    So, if everything that can be tapped resolves to false, you're left with "nothing was tapped". I guess you're sort of doing the inverse, and instead check "was nothing tapped" first, then handle all the tappable objects. But I think my version would spare you having to iterate over all objects that cannot be tapped and instead only iterate over the objects that can be tapped.

    That is essentially what I am doing instead, where the DoThing is nothing.

    Pusedo Code of essentially what I have -

    for each family in familyList
    {
    if any object in family is tapped
    	{ StopProcessing(); return; }
    }
    

    The Real Issue however is that the objects I am trying to determine if I have tapped are objects that have their actions sorted in the event sheet. The main example is the Pause Button. I have On Touch on the event sheet to activate the Pause function, but then the Scripting side has no visibility of that.

    The "other problem" is that layers that are set to transparent will bubble the UI Pointer event down to each lower layer.

    So where my layers are (with notations for click events):

    1. UIOverlay
    2. <-- pause button OnTap Coords
    3. Enemy
    4. Towers
    5. <-- sub-popup menu
    6. Grid
    7. <-- still spawns new tower through OnTouch/TouchPointer check on tick

    While it would be nice to go Layout -> Layer -> Objects On Layer unfortunately that linkage isn't there right now

    What I have also seem to have found through sheer accident is that once you mark a layer as "not interactive", essentially it turns off the collisions for itself and ALL lower layers so that is there as a potential work around. This does solve the while clicking around on the Menu problem, but I still will need to check if we select any other objects while the main level is running.

    The last experiment I will end up trying is adding a script element to the event sheet to determine if the same PointerEvent object is passed around, or only copies of the original

    If Yes, then essentially I MIGHT be able to include an Ignore flag to the PointerEvent

  • So in the end I kinda bit the bullet and went for the obvious - iterate over the objects and do the checks for each WI unfortunately.

    For this current project each layout is the size of the viewport so cssPxToLayer shouldn't have any issues with parallax/floating UI, but ill add a "dictionary" for layers so I don't have to do the coordinate conversion for every object!

     let mouseXYAr = this.towerLayer.cssPxToLayer(pointerEvent.clientX, pointerEvent.clientY);
    
     // go through each family of objects to ignore
     // if we find we are clicking on litterally any, return out do nothing
     const familiesToIgnoreIfClick = ["UITextButtons", "UIButtons", "UIOverlays"];
     
     // do an any of families has any clicked items, if yes, bail method 
     if(familiesToIgnoreIfClick.some(family => 
     this.runtime.objects[family].getAllInstances().some(
     wi => 
     wi.layer.isSelfAndParentsVisible &&
     wi.layer.isSelfAndParentsInteractive &&
     wi.isVisible &&
     wi.containsPoint(mouseXYAr[0], mouseXYAr[1]))
     ))
     {
     // bail out if ANY
     return;
     }
    
  • Currently I am working on a Tower Defence game using a combination of Scripting for the data tracking and Event Sheets for the basic behaviours.

    I have the Towers being placed on the Tilemap Grid reliably, and can determine when I select a Tower object on the Tap before doing the grid coords.

    While I CAN go manually through families of objects I setup, like the on-screen Pause Button, is there an easy/simple way to determine if I HAVENT tapped anything on a layer? Or at least a way of getting all objects within a layer without having to iterate over the whole object set?

    Please find below my current OnTap "event" code, and the circles of the towers that have placed after I "tap" a text item

     OnTap = (pointerEvent) =>
     { 
     if(this.gameOverHit) return;
     
     let mouseXYAr = this.towerLayer.cssPxToLayer(pointerEvent.clientX, pointerEvent.clientY);
     const towers = this.runtime.objects.Towers.getAllInstances();
    
    
     const foundTower = towers.find(s => s.containsPoint(mouseXYAr[0], mouseXYAr[1]));
     if(foundTower == null) 
     {
     mouseXYAr = this.gridLayer.cssPxToLayer(pointerEvent.clientX, pointerEvent.clientY);
     if(this.tilemap.containsPoint(mouseXYAr[0], mouseXYAr[1]))
     { 
     const tileSizeX = this.tilemap.tileWidth;
     const tileSizeY = this.tilemap.tileHeight;
     
     const cellCoords = [Math.floor(mouseXYAr[0]/tileSizeX), Math.floor(mouseXYAr[1]/tileSizeY)]
     const towerMapResult = this.TowerMap[cellCoords[0]][cellCoords[1]];
     if(towerMapResult != true)
     {
     if(this.currentMoney >= 10)
     {
     this.UpdateMoney(-10);
     this.runtime.objects.basicTower.createInstance(2, cellCoords[0]*tileSizeX + tileSizeX/2, cellCoords[1]*tileSizeY + tileSizeY/2, true, "");
     this.TowerMap[cellCoords[0]][cellCoords[1]] = true;
     }
     }
     }
     }
     }
    
    
  • 14 posts