Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions examples/react/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import './App.css';
import arrow from './assets/arrow.png';
import lynxLogo from './assets/lynx-logo.png';
import reactLynxLogo from './assets/react-logo.png';
import { useFlappy } from './useFlappy.js';

export function App() {
const [alterLogo, setAlterLogo] = useState(false);
const [logoY, jump] = useFlappy();

useEffect(() => {
console.info('Hello, ReactLynx');
Expand All @@ -18,11 +20,15 @@ export function App() {
}, []);

return (
<view>
<view bindtap={jump}>
<view className='Background' />
<view className='App'>
<view className='Banner'>
<view className='Logo' bindtap={onTap}>
<view
className='Logo'
style={{ transform: `translateY(${logoY}px)` }}
bindtap={onTap}
>
{alterLogo
? <image src={reactLynxLogo} className='Logo--react' />
: <image src={lynxLogo} className='Logo--lynx' />}
Expand Down
76 changes: 76 additions & 0 deletions examples/react/src/lib/flappy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Framework-agnostic flappy-bird physics engine.
*
* Manages gravity, jump impulse, and a 60fps game loop.
* Wire it up to any UI framework by calling `jump()` on tap
* and reading `getY()` in the loop callback.
*/

export interface FlappyOptions {
/** Downward acceleration per frame (default 0.6) */
gravity?: number;
/** Upward impulse per tap — negative value (default -12) */
jumpForce?: number;
/** Impulse stacking factor for rapid taps (default 0.6) */
stackFactor?: number;
/** Frame interval in ms (default 16 ≈ 60fps) */
frameMs?: number;
}

export type OnUpdate = (y: number) => void;

export interface FlappyEngine {
/** Call on each tap to apply upward impulse. */
jump(): void;
/** Current Y offset (0 = ground, negative = airborne). */
getY(): number;
/** Stop the game loop and clean up. */
destroy(): void;
}

export function createFlappy(
onUpdate: OnUpdate,
options: FlappyOptions = {},
): FlappyEngine {
const {
gravity = 0.6,
jumpForce = -12,
stackFactor = 0.6,
frameMs = 16,
} = options;

let y = 0;
let velocity = 0;
let timer: ReturnType<typeof setTimeout> | null = null;

function loop() {
velocity += gravity;
y += velocity;
if (y >= 0) {
y = 0;
velocity = 0;
timer = null;
onUpdate(y);
return;
}
onUpdate(y);
timer = setTimeout(loop, frameMs);
}

function jump() {
// Stack impulse on rapid taps, clamped to one full jumpForce
velocity = Math.max(velocity + jumpForce * stackFactor, jumpForce);
if (!timer) {
loop();
}
}

function destroy() {
if (timer) {
clearTimeout(timer);
timer = null;
}
}

return { jump, getY: () => y, destroy };
}
47 changes: 47 additions & 0 deletions examples/react/src/useFlappy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useCallback, useEffect, useRef, useState } from '@lynx-js/react';

import { createFlappy } from './lib/flappy.js';
import type { FlappyEngine, FlappyOptions } from './lib/flappy.js';

/**
* React hook for flappy-bird physics.
*
* Returns `[y, jump]` — a state value and a stable callback.
* The game loop runs automatically; cleanup happens on unmount.
* Options are read once on mount and not reactive to later changes.
*
* @example
* ```tsx
* function Bird() {
* const [y, jump] = useFlappy();
* return (
* <view bindtap={jump} style={{ transform: `translateY(${y}px)` }}>
* <text>Tap me!</text>
* </view>
* );
* }
* ```
*/
export function useFlappy(
options?: FlappyOptions,
): [number, () => void] {
const [y, setY] = useState(0);
const engineRef = useRef<FlappyEngine | null>(null);

engineRef.current ??= createFlappy((newY) => {
setY(newY);
}, options);

useEffect(() => {
return () => {
engineRef.current?.destroy();
};
}, []);

const jump = useCallback(() => {
'background-only';
engineRef.current?.jump();
}, []);

return [y, jump];
}
10 changes: 8 additions & 2 deletions packages/rspeedy/create-rspeedy/template-react-js/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import './App.css'
import arrow from './assets/arrow.png'
import lynxLogo from './assets/lynx-logo.png'
import reactLynxLogo from './assets/react-logo.png'
import { useFlappy } from './useFlappy.js'

export function App() {
const [alterLogo, setAlterLogo] = useState(false)
const [logoY, jump] = useFlappy()

useEffect(() => {
console.info('Hello, ReactLynx')
Expand All @@ -18,11 +20,15 @@ export function App() {
}, [])

return (
<view>
<view bindtap={jump}>
<view className='Background' />
<view className='App'>
<view className='Banner'>
<view className='Logo' bindtap={onTap}>
<view
className='Logo'
style={{ transform: `translateY(${logoY}px)` }}
bindtap={onTap}
>
{alterLogo
? <image src={reactLynxLogo} className='Logo--react' />
: <image src={lynxLogo} className='Logo--lynx' />}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Framework-agnostic flappy-bird physics engine.
*
* Manages gravity, jump impulse, and a 60fps game loop.
* Wire it up to any UI framework by calling `jump()` on tap
* and reading `getY()` in the loop callback.
*
* @param {(y: number) => void} onUpdate
* @param {object} [options]
* @param {number} [options.gravity=0.6]
* @param {number} [options.jumpForce=-12]
* @param {number} [options.stackFactor=0.6]
* @param {number} [options.frameMs=16]
* @returns {{ jump: () => void, getY: () => number, destroy: () => void }}
*/
export function createFlappy(onUpdate, options = {}) {
const {
gravity = 0.6,
jumpForce = -12,
stackFactor = 0.6,
frameMs = 16,
} = options

let y = 0
let velocity = 0
let timer = null

function loop() {
velocity += gravity
y += velocity
if (y >= 0) {
y = 0
velocity = 0
timer = null
onUpdate(y)
return
}
onUpdate(y)
timer = setTimeout(loop, frameMs)
}

function jump() {
// Stack impulse on rapid taps, clamped to one full jumpForce
velocity = Math.max(velocity + jumpForce * stackFactor, jumpForce)
if (!timer) {
loop()
}
}

function destroy() {
if (timer) {
clearTimeout(timer)
timer = null
}
}

return { jump, getY: () => y, destroy }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useCallback, useEffect, useRef, useState } from '@lynx-js/react'

import { createFlappy } from './lib/flappy.js'

/**
* React hook for flappy-bird physics.
*
* Returns `[y, jump]` — a state value and a stable callback.
* The game loop runs automatically; cleanup happens on unmount.
* Options are read once on mount and not reactive to later changes.
*
* @param {object} [options]
* @returns {[number, () => void]}
*/
export function useFlappy(options) {
const [y, setY] = useState(0)
const engineRef = useRef(null)

if (!engineRef.current) {
engineRef.current = createFlappy((newY) => {
setY(newY)
}, options)
}

useEffect(() => {
return () => {
engineRef.current?.destroy()
}
}, [])

const jump = useCallback(() => {
'background only'
engineRef.current?.jump()
}, [])

return [y, jump]
}
10 changes: 8 additions & 2 deletions packages/rspeedy/create-rspeedy/template-react-ts/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import './App.css'
import arrow from './assets/arrow.png'
import lynxLogo from './assets/lynx-logo.png'
import reactLynxLogo from './assets/react-logo.png'
import { useFlappy } from './useFlappy.js'

export function App() {
const [alterLogo, setAlterLogo] = useState(false)
const [logoY, jump] = useFlappy()

useEffect(() => {
console.info('Hello, ReactLynx')
Expand All @@ -18,11 +20,15 @@ export function App() {
}, [])

return (
<view>
<view bindtap={jump}>
<view className='Background' />
<view className='App'>
<view className='Banner'>
<view className='Logo' bindtap={onTap}>
<view
className='Logo'
style={{ transform: `translateY(${logoY}px)` }}
bindtap={onTap}
>
{alterLogo
? <image src={reactLynxLogo} className='Logo--react' />
: <image src={lynxLogo} className='Logo--lynx' />}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Framework-agnostic flappy-bird physics engine.
*
* Manages gravity, jump impulse, and a 60fps game loop.
* Wire it up to any UI framework by calling `jump()` on tap
* and reading `getY()` in the loop callback.
*/

export interface FlappyOptions {
/** Downward acceleration per frame (default 0.6) */
gravity?: number
/** Upward impulse per tap — negative value (default -12) */
jumpForce?: number
/** Impulse stacking factor for rapid taps (default 0.6) */
stackFactor?: number
/** Frame interval in ms (default 16 ≈ 60fps) */
frameMs?: number
}

export type OnUpdate = (y: number) => void

export interface FlappyEngine {
/** Call on each tap to apply upward impulse. */
jump(): void
/** Current Y offset (0 = ground, negative = airborne). */
getY(): number
/** Stop the game loop and clean up. */
destroy(): void
}

export function createFlappy(
onUpdate: OnUpdate,
options: FlappyOptions = {},
): FlappyEngine {
const {
gravity = 0.6,
jumpForce = -12,
stackFactor = 0.6,
frameMs = 16,
} = options

let y = 0
let velocity = 0
let timer: ReturnType<typeof setTimeout> | null = null

function loop() {
velocity += gravity
y += velocity
if (y >= 0) {
y = 0
velocity = 0
timer = null
onUpdate(y)
return
}
onUpdate(y)
timer = setTimeout(loop, frameMs)
}

function jump() {
// Stack impulse on rapid taps, clamped to one full jumpForce
velocity = Math.max(velocity + jumpForce * stackFactor, jumpForce)
if (!timer) {
loop()
}
}

function destroy() {
if (timer) {
clearTimeout(timer)
timer = null
}
}

return { jump, getY: () => y, destroy }
}
Loading
Loading