diff --git a/package.json b/package.json index 89290b1..6141431 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vcs-game-maker", - "version": "0.18.1", + "version": "0.19.0", "private": true, "scripts": { "serve": "vue-cli-service serve", diff --git a/src/blocks/icon.js b/src/blocks/icon.js index 2b6c1ce..263b989 100644 --- a/src/blocks/icon.js +++ b/src/blocks/icon.js @@ -10,3 +10,4 @@ export const SCORE_ICON = String.fromCodePoint(0x1F4AF); export const BACKGROUND_ICON = String.fromCodePoint(0x1F304); export const DICE_ICON = String.fromCodePoint(0x1F3B2); export const SOUND_ICON = String.fromCodePoint(0x1F509); +export const ANIMATION_ICON = String.fromCodePoint(0x1F3AC); diff --git a/src/blocks/sprites.js b/src/blocks/sprites.js index 408a98c..30e08de 100644 --- a/src/blocks/sprites.js +++ b/src/blocks/sprites.js @@ -1,11 +1,12 @@ import * as Blockly from 'blockly/core'; -import {PLAYER_ICON, MISSILE_ICON, BALL_ICON, COLOR_ICON, HEIGHT_ICON} from './icon'; +import {PLAYER_ICON, MISSILE_ICON, BALL_ICON, COLOR_ICON, HEIGHT_ICON, ANIMATION_ICON} from './icon'; const buildPlayerOptions = (name) => [ ['\u2195 X', `${name}x`], ['\u2195 Y', `${name}y`], [COLOR_ICON + ' Color', `${name}realcolor`], + [ANIMATION_ICON + ' Animation', `${name}animation`], ]; const buildMissileOptions = (name) => [ diff --git a/src/components/PlayerEditor.vue b/src/components/PlayerEditor.vue index 476b005..3936f5e 100644 --- a/src/components/PlayerEditor.vue +++ b/src/components/PlayerEditor.vue @@ -8,6 +8,46 @@ <v-list-item-content> <v-list-item-title> <v-text-field label="Animation name" v-model="animation.name" /> + + <v-menu + top + v-if="state.animations.length > 1" + > + <template v-slot:activator="{ on, attrs }"> + <v-btn + color="red" + title="Delete this animation" + fab + small + absolute + top + right + v-bind="attrs" + v-on="on" + > + <v-icon>mdi-delete</v-icon> + </v-btn> + </template> + + <v-card> + <v-card-title>Delete this animation?</v-card-title> + <v-list> + <v-list-item @click="handleDeleteAnimation(animation)"> + <v-list-item-icon> + <v-icon>mdi-check</v-icon> + </v-list-item-icon> + <v-list-item-title>Yes, delete</v-list-item-title> + </v-list-item> + <v-list-item> + <v-list-item-icon> + <v-icon>mdi-cancel</v-icon> + </v-list-item-icon> + <v-list-item-title>No, don't delete</v-list-item-title> + </v-list-item> + </v-list> + </v-card> + </v-menu> + </v-list-item-title> <v-list> <v-list-item @@ -18,6 +58,7 @@ <div class="pixel-editor-container"> <v-menu top + v-if="animation.frames.length > 1" > <template v-slot:activator="{ on, attrs }"> <v-btn @@ -71,25 +112,39 @@ </div> </v-list-item> </v-list> + <v-btn + class="add-frame-buttom" + color="primary" + title="Add animation frame" + dark + absolute + right + rounded + @click="handleAddFrame(animation, frame)" + > + <v-icon>mdi-plus</v-icon> + <div>Add frame</div> + </v-btn> </v-list-item-content> </v-list-item> </v-list> + + <v-btn + color="secondary" + title="Add animation" + dark + absolute + right + rounded + @click="handleAddAnimation" + > + <v-icon>mdi-plus</v-icon> + <div>Add animation</div> + </v-btn> </v-card-text> </v-card> - <v-btn - class="add-frame-buttom" - color="primary" - title="Add animation frame" - dark - absolute - right - fab - @click="handleAddFrame" - > - <v-icon>mdi-plus</v-icon> - </v-btn> </div> </template> <script> @@ -104,11 +159,23 @@ export default defineComponent({ components: {PixelEditor}, props: ['storageFactory', 'title', 'fgColor'], setup(props) { + const getMaxId = (elements) => { + return max(elements.map((o) => o.id))||0; + }; + const playerStorage = props.storageFactory(); const state = computed({ get() { try { - return processPlayerStorageDefaults(playerStorage); + const player = processPlayerStorageDefaults(playerStorage); + let nextId = getMaxId(player.animations); + for (const animation of player.animations) { + if (!animation.id) { + animation.id = nextId; + nextId++; + } + } + return player; } catch (e) { console.error('Error loading player 0 from local storage', e); return DEFAULT_SPRITES; @@ -125,9 +192,10 @@ export default defineComponent({ }; const instance = getCurrentInstance(); - const handleAddFrame = () => { - const frames = state.value.animations[0].frames; - const maxId = max(frames.map((o) => o.id)) || 0; + + const handleAddFrame = (animation, frame) => { + const frames = animation.frames; + const maxId = getMaxId(frames); const newFrame = { id: maxId+1, duration: 10, @@ -142,7 +210,7 @@ export default defineComponent({ '........'), }; - state.value.animations[0].frames.push(newFrame); + animation.frames.push(newFrame); handleChildChange(); instance.proxy.$forceUpdate(); @@ -155,7 +223,28 @@ export default defineComponent({ instance.proxy.$forceUpdate(); }; - return {state, handleChildChange, handleAddFrame, handleDeleteFrame, props}; + const handleAddAnimation = () => { + const newAnimation = structuredClone(state.value.animations[state.value.animations.length-1]); + state.value.animations.push({ + ...newAnimation, + id: getMaxId(state.value.animations) + 1, + }); + + handleChildChange(); + instance.proxy.$forceUpdate(); + }; + + const handleDeleteAnimation = (animation) => { + state.value.animations = state.value.animations.filter(({id}) => id != animation.id); + console.info('Deleted ', animation); + handleChildChange(); + instance.proxy.$forceUpdate(); + }; + + return {state, handleChildChange, + handleAddFrame, handleDeleteFrame, + handleAddAnimation, handleDeleteAnimation, + props}; }, }); </script> diff --git a/src/generators/bbasic.bb.hbs b/src/generators/bbasic.bb.hbs index 38cdc7d..00db0d5 100644 --- a/src/generators/bbasic.bb.hbs +++ b/src/generators/bbasic.bb.hbs @@ -45,7 +45,9 @@ end dim player1size = s dim player0realcolor = r dim player1realcolor = q - + dim player0animation = p + dim player1animation = o + newbackground = 1 player0x = 75 : player0y = 75 @@ -58,6 +60,7 @@ end player0size = $30 COLUBK = $C4 COLUPF = $0E + player0animation = 1 rem ************************************************************************** rem Main loop: diff --git a/src/generators/bbasic.js b/src/generators/bbasic.js index 15f63a5..d6bed24 100644 --- a/src/generators/bbasic.js +++ b/src/generators/bbasic.js @@ -370,49 +370,68 @@ Blockly.BBasic.generateBackgrounds = function() { }; Blockly.BBasic.generateAnimations = function() { - const processAnimation = (name, playerStorage) => { - let playerData = null; - try { - playerData = processPlayerStorageDefaults(playerStorage); - } catch (e) { - console.error(`Failed to load ${name} data`, e); - } - - if (!playerData) { - return ''; - } - - const animation = playerData.animations[0]; + const processAnimation = (name, animation, animationIndex) => { if (!animation) { return ''; } + const animationLabel = `${name}animation${animationIndex}`; const totalDuration = sumBy(animation.frames, (frame) => frame.duration || 0); let frameLimit = 0; const stateMachine = animation.frames.map((frame, frameIndex) => { frameLimit += frame.duration || 0; const pixelSource = frame.pixels.slice().reverse().map((row) => ' %' + row.join('')); - const endLabel = `${name}frame${frameIndex}End`; + const endLabel = `${animationLabel}frame${frameIndex}End`; const skipCondition = ` if ${name}frame > ${frameLimit} then goto ${endLabel}\n`; return skipCondition + ` ${name}:\n` + pixelSource.join('\n') + '\nend\n' + - ` goto ${name}animationEnd\n` + + ` goto ${animationLabel}animationEnd\n` + endLabel; }); - return ` rem Animation ${animation.name}:\n` + + return ` rem Animation ${animationIndex} ${animation.name} for ${name}:\n\n` + ` ${name}frame = ${name}frame + 1\n` + ` if ${name}frame = ${totalDuration} then ${name}frame = 0\n\n` + stateMachine.join('\n\n') + - `\n\n${name}animationEnd`; + `\n\n${animationLabel}animationEnd`; + }; + + const processAnimations = (name, playerStorage) => { + let playerData = null; + try { + playerData = processPlayerStorageDefaults(playerStorage); + } catch (e) { + console.error(`Failed to load ${name} data`, e); + } + + if (!playerData) { + return ''; + } + + const animationsLabel = `${name}animations`; + const animationsEndLabel = `${animationsLabel}End`; + const getAnimationStartLabel = (animationIndex) => `${name}animation${animationIndex}Start`; + + return ` rem Animations for ${name}:\n` + + playerData.animations.map((animation, animationIndex) => { + if (!animationIndex) return ''; + return ` if ${name}animation = ${animationIndex} then goto ${getAnimationStartLabel(animationIndex)}`; + }).join('\n') + + '\n\n' + + playerData.animations.map((animation, animationIndex) => { + return `${getAnimationStartLabel(animationIndex)}\n\n` + + processAnimation(name, animation, animationIndex) + + `\n goto ${animationsEndLabel}`; + }).join('\n\n') + + `\n\n${animationsEndLabel}`; }; - const player0Code = processAnimation('player0', usePlayer0Storage()); - const player1Code = processAnimation('player1', usePlayer1Storage()); + const player0Code = processAnimations('player0', usePlayer0Storage()); + const player1Code = processAnimations('player1', usePlayer1Storage()); return player0Code + '\n\n\n' + player1Code; }; import background from './bbasic/background';