The one-way tolerance angle is a field: a one-way collider and a one-way tile gain One-way angle (0 to 90 degrees, default 45), a contact blocks when its normal is within that angle of the blocking side, without a value the limit is bit-identical to before, and the rule holds for Move, Raycast and dynamic bodies in Rapier; tiles with different angles never merge into one rectangle, the scene view draws the angle as a cone in the middle of the thick side, and the inspector shows Blocks from and One-way angle only while One-way is on, through the new schema option shownWith (#159)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d4ea04d4d6
commit
5385d9e152
14 files changed
+454
-62
No files matched your search
@@ -19,7 +19,12 @@ import { findAsset, type Project } from '../../core/model/Project';
|
||||
import type { PropertyBag, PropertyValue } from '../../core/model/Properties';
|
||||
import { isGlobal, readScope, scopeOf } from '../../core/model/Scope';
|
||||
import { type StructFieldKind, VARIABLE_KINDS } from '../../core/properties/kinds';
|
||||
import { type PropertyDef, schemaForAsset, type StructFieldDef } from '../../core/properties/PropertySchema';
|
||||
import {
|
||||
isRowShown,
|
||||
type PropertyDef,
|
||||
schemaForAsset,
|
||||
type StructFieldDef,
|
||||
} from '../../core/properties/PropertySchema';
|
||||
import { markedAssetSchema } from '../../core/properties/deviation';
|
||||
import { isModified, resolveAssetProps } from '../../core/properties/resolve';
|
||||
import { Dropdown, HStack, Label, Menu, Theme } from '../../ui';
|
||||
@@ -35,15 +40,17 @@ import { assetValue } from './valueSpecs';
|
||||
const newField = (key: string, kind: StructFieldKind): StructFieldDef => ({ key, kind });
|
||||
|
||||
/**
|
||||
* Structure-defining facts of an asset selection: the schema keys and kinds, plus the fields of a
|
||||
* struct – those are rows of their own, so adding one has to rebuild the section.
|
||||
* Structure-defining facts of an asset selection: the schema keys and kinds of the rows shown, plus
|
||||
* the fields of a struct – those are rows of their own, so adding one has to rebuild the section.
|
||||
*/
|
||||
export function assetSignature(project: Project, assetId: string): string {
|
||||
const asset = findAsset(project, assetId);
|
||||
if (!asset) return 'missing';
|
||||
// A machine's own rows are none, so its scope has to be named here or switching it rebuilds nothing.
|
||||
const machine = asset.type === 'statemachine' ? `|${scopeOf(asset)}` : '';
|
||||
return schemaForAsset(project, asset).map(defSignature).join(',') + machine;
|
||||
const values = resolveAssetProps(project, asset);
|
||||
const rows = schemaForAsset(project, asset).filter((def) => isRowShown(def, values));
|
||||
return rows.map(defSignature).join(',') + machine;
|
||||
}
|
||||
|
||||
export function defSignature(def: PropertyDef): string {
|
||||
@@ -101,7 +108,7 @@ export function refreshAssetSection(host: InspectorHost, assetId: string): boole
|
||||
function buildProps(host: InspectorHost, asset: Asset, schema: readonly PropertyDef[]): void {
|
||||
const project = () => host.state.project;
|
||||
const values = resolveAssetProps(project(), asset);
|
||||
for (const def of schema) {
|
||||
for (const def of schema.filter((d) => isRowShown(d, values))) {
|
||||
const row = createFieldRow(
|
||||
host,
|
||||
def,
|
||||
|
||||
@@ -31,6 +31,7 @@ import type { SceneObject } from '../../core/model/SceneObject';
|
||||
import {
|
||||
COMMON_PROPS,
|
||||
isCommonProp,
|
||||
isRowShown,
|
||||
isTransformProp,
|
||||
type PropertyDef,
|
||||
schemaFor,
|
||||
@@ -65,7 +66,7 @@ export function objectRows(project: Project, asset: Asset, object: SceneObject):
|
||||
: schemaForObject(project, asset, object).filter((def) => !isCommonProp(def.key));
|
||||
}
|
||||
|
||||
/** Structure-defining facts of an object selection: parent, template link, schema and painting. */
|
||||
/** Structure-defining facts of an object selection: parent, template link, the rows shown and painting. */
|
||||
export function objectSignature(
|
||||
state: EditorState,
|
||||
holderId: string,
|
||||
@@ -84,7 +85,8 @@ export function objectSignature(
|
||||
: isLiveInstance(project, holder, object)
|
||||
? 'instance'
|
||||
: '';
|
||||
const rows = schemaForObject(project, asset, object);
|
||||
const values = resolveSceneObjectProps(project, asset, object);
|
||||
const rows = schemaForObject(project, asset, object).filter((def) => isRowShown(def, values));
|
||||
// A tilemap warns when it is placed more than once, so the number of placements shapes it too.
|
||||
const placed = asset.type === 'tilemap' ? `|placed:${findUsages(project, asset.id).length}` : '';
|
||||
return `${object.parentId}|${kind}${placed}|` + rows.map(defSignature).join(',');
|
||||
@@ -202,7 +204,7 @@ export function buildObjectSection(host: InspectorHost, holderId: string, object
|
||||
|
||||
// Asset properties (or behavior variables) with overrides; the switches are shown above.
|
||||
const values = resolveSceneObjectProps(host.state.project, asset, object);
|
||||
const own = objectRows(host.state.project, asset, object);
|
||||
const own = objectRows(host.state.project, asset, object).filter((def) => isRowShown(def, values));
|
||||
if (own.length > 0 || asset.type === 'behavior')
|
||||
addTitle(host, asset.type === 'behavior' ? 'VARIABLES' : 'PROPERTIES');
|
||||
if (own.length === 0 && asset.type === 'behavior') addNote(host, 'The behavior declares no variables.');
|
||||
|
||||
@@ -61,7 +61,7 @@ import {
|
||||
import { gripAt, withCornerAfter, withCornerMoved, withoutCorner } from '../polygonEdit';
|
||||
import { collectHolderItems, type DrawItem } from '../../viewport/items';
|
||||
import type { LayerFocus } from '../../viewport/tilemap';
|
||||
import { dashedLine, drawSourceIcon, markBlockingSide, placeNode } from '../../viewport/symbols';
|
||||
import { CONE_LENGTH, dashedLine, drawSourceIcon, markBlockingSide, placeNode } from '../../viewport/symbols';
|
||||
import { screenToWorld, type View, worldToScreen } from '../../viewport/view';
|
||||
import { type CastRay, RAY_SHOWN, type World } from '../../runtime/World';
|
||||
import { FreeViewport } from './FreeViewport';
|
||||
@@ -879,7 +879,8 @@ export class SceneViewport extends FreeViewport {
|
||||
stroke(boundary.solid, width);
|
||||
fill(oneWay.solids, oneWay.outlines, 0.06);
|
||||
stroke(boundary.oneWay, width / 2);
|
||||
// A platform's top is what blocks, so that is what stands out.
|
||||
// A platform's top is what blocks, so that is what stands out, with the cone it lands within.
|
||||
const length = CONE_LENGTH / (scale * scaleOf(item.m));
|
||||
for (const rect of oneWay.solids) {
|
||||
const a = cellOrigin(item.props, rect.x, rect.y);
|
||||
const b = cellOrigin(item.props, rect.x + rect.w, rect.y + rect.h);
|
||||
@@ -889,6 +890,7 @@ export class SceneViewport extends FreeViewport {
|
||||
{ x: 0, y: -1 },
|
||||
width * 2,
|
||||
color,
|
||||
{ angle: rect.angle, length },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user