thepeach's Recent Forum Activity

  • Is it possible to determine when a tagged master keyframe has been reached in a timeline using the scripting interfaces?

    In event sheets, this feature is equivalent to Timeline Controller’s "on keyframe reached" event.

  • Looks like a fix is on the way (#7998). Apologies for the duplicate report.

  • In r390, this error message is logged to the console when destroying a Sprite object with a behavior (e.g. the Solid behavior).

    TypeError: this._iScriptInterface._release is not a function at BehaviorInstance.Release (behaviorInstance.js:2:401) ...

    The error does not occur in r388 (stable), and can be reproduced with the following code (where the instance returned from runtime.objects.Sprite.getFirstInstance() is a Sprite object with a behavior):

    main.js

    runOnStartup(async runtime => {
    	runtime.addEventListener('beforeprojectstart', () => {
    		runtime.objects.Sprite.getFirstInstance()?.destroy();
    	});
    });
    
  • Glad to help. Really appreciate you getting the fix out so quickly!

  • Try Construct 3

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

    Try Now Construct 3 users don't see these ads
  • Is the TypeScript definition for IWorldInstance's testOverlapSolid() method correct? The docs say:

    This returns the instance interface class for the first instance with the solid behavior that was found to overlap this instance, or null if none.

    However, the type definition in IWorldInstance.d.ts is testOverlapSolid(): boolean;.

    I'm currently working around this by using the ICollisionEngine interface directly (e.g. runtime.collisions.testOverlapSolid(myInstance)).

  • Gotcha. Yeah, those would be nice performance optimizations to make. Thanks again for the suggestions.

  • Thanks for the feedback! That's definitely worth considering.

    With that said, unless the current character is a whitespace, I don't believe line 69 tests if the next word will wrap (since the logical AND expression is a short-circuit operator).

  • If anyone needs a workaround, here's my quick implementation (in TypeScript). There's certainly room for improvement, but it solves my issue for now:

    Example Usage

    import Typewriter from './Typewriter.js';
    
    // ...
    
    // Create a new Typewriter instance.
    const textInst = runtime.objects.Text.getFirstInstance()!;
    const typewriter = new Typewriter(textInst);
    
    // Callback executed when a character is added.
    typewriter.onTypeHandler = (char, _) => {
    	console.log(`Typed character: ${char}`);
    }
    
    // Callback executed when typing finishes.
    typewriter.onFinishedHandler = text => {
    	console.log(`Finished typing text: ${text}`);
    }
    
    // Start the effect with the specified arguments.
    const text = 'Hello, World! This is a typewriter effect with callbacks.';
    const charsPerSecond = text.length / 30;
    typewriter.start(text, charsPerSecond);
    

    Typewriter.ts

    export default class Typewriter {
    	readonly textInstance: ITextInstance;
    
    	onTypeHandler: ((char: string, text: string) => void) | null = null;
    	onFinishedHandler: ((text: string) => void) | null = null;
    
    	get isTyping() { return this.charIndex > -1; }
    
    	private text = '';
    	private charIndex = -1;
    	private charsPerSecond = 0;
    	private timeSinceLastType = 0;
    
    	constructor(textInstance: ITextInstance) {
    		this.textInstance = textInstance;
    		this.onTick = this.onTick.bind(this);
    		this.textInstance.runtime.addEventListener('tick', this.onTick);
    	}
    
    	destroy() {
    		this.textInstance.runtime.removeEventListener('tick', this.onTick);
    	}
    
    	start(text: string, duration: number) {
    		if (this.isTyping) this.finish();
    
    		this.text = text;
    
    		if (text.length > 0 && duration > 0) {
    			this.charIndex = 0;
    			this.charsPerSecond = text.length / duration;
    			this.timeSinceLastType = 0;
    			this.textInstance.text = '';
    		} else {
    			this.finish();
    		}
    	}
    
    	finish() {
    		this.charIndex = -1;
    		this.textInstance.text = this.text;
    		this.onFinishedHandler?.(this.text);
    	}
    
    	private onTick() {
    		if (!this.isTyping) return;
    
    		while (this.isTyping && this.timeSinceLastType >= 1 / this.charsPerSecond) {
    			this.type();
    			this.timeSinceLastType -= 1 / this.charsPerSecond;
    		}
    
    		this.timeSinceLastType += this.textInstance.runtime.dt;
    	}
    
    	private type() {
    		if (this.charIndex === this.text.length - 1) {
    			this.finish();
    			return;
    		}
    
    		// Type the next character.
    		const char = this.text.charAt(this.charIndex);
    		this.textInstance.text += char;
    		this.onTypeHandler?.(char, this.textInstance.text);
    		this.charIndex++;
    
    		// Add a line break if the next word would exceed the text width.
    		if (char === ' ' && this.textInstance.wordWrapMode === 'word' && this.nextWordWillWrap()) {
    			this.textInstance.text =
    				this.textInstance.text.substring(0, this.charIndex - 1) + '\n' +
    				this.textInstance.text.substring(this.charIndex);
    		}
    	}
    
    	private nextWordWillWrap(): boolean {
    		const lines = this.textInstance.text.split('\n');
    		const currentLine = lines[lines.length - 1] || '';
    		const nextSpaceCharIndex = this.text.indexOf(' ', this.charIndex);
    		const nextWordEndIndex = (nextSpaceCharIndex === -1) ? undefined : nextSpaceCharIndex;
    		const nextWord = this.text.substring(this.charIndex, nextWordEndIndex);
    		const lineWidth = this.getLineWidth(currentLine + nextWord);
    
    		return lineWidth > this.textInstance.width;
    	}
    
    	private getLineWidth(text: string): number {
    		// Cache the text instance's state.
    		const originalText = this.textInstance.text;
    		const originalWidth = this.textInstance.width;
    
    		// Measure the full width of the line.
    		this.textInstance.text = text;
    		this.textInstance.width = Infinity;
    		const lineWidth = this.textInstance.textWidth;
    
    		// Restore the text instance's state.
    		this.textInstance.text = originalText;
    		this.textInstance.width = originalWidth;
    
    		return lineWidth;
    	}
    }
    
  • Using the typewriterText(str, duration) method on ITextInstance, is there a way to track added characters or know when typing ends? The instance's text property always reflects the value of the str argument, not the current text state. Here's a simplified code example:

    runOnStartup(async runtime => {
    	runtime.addEventListener('beforeprojectstart', () => onBeforeProjectStart(runtime));
    });
    
    async function onBeforeProjectStart(runtime: IRuntime) {
    	const str = 'Hello, World! This is a typewriter effect.';
    	const duration = str.length / 30;
    	const textInst = runtime.objects.Text.getFirstInstance()!;
    	textInst.typewriterText(str, duration);
    
    	runtime.addEventListener('tick', () => {
    		console.log(textInst.text === str); // Always true
    	});
    }
    
  • Ah, makes sense. Thanks very much for the clarification.

  • Works great for the “Simple” mode!

    Should this solution also work for the “Advanced” mode? I'm running into an issue:

    [JSC_UNDEFINED_VARIABLE] variable InstanceType is undeclared

    Main.ts

    import MySprite from './MySprite.js';
    
    runOnStartup(async runtime => {
     runtime.objects.MySprite.setInstanceClass(MySprite);
    });
    

    MySprite.ts

    export default class MySprite extends InstanceType.MySprite {
    }
    
  • Thanks for the info, Ashley. I suspected that might be the case. I will minify after exporting for now.

thepeach's avatar

thepeach

Member since 10 Dec, 2022

None one is following thepeach yet!

Connect with thepeach